dist / tools / diagnostics.js
dist / tools / diagnostics.js
"use strict";
/**
* @file diagnostics.ts
* B2: maestro_status — read-only self-diagnostics for the agent:
* tool-call journal rollup, loop-guard counters, denial state,
* context-gauge reading, queue depths, process vitals.
* Pure parts (summarizeJournal/formatStatus) are unit-tested;
* the tool only gathers live data and renders.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.summarizeJournal = summarizeJournal;
exports.formatStatus = formatStatus;
exports.maestroStatusTool = void 0;
const fs_1 = require("fs/promises");
const path_1 = require("path");
const os_1 = require("os");
const sdk_1 = require("@lmstudio/sdk");
const loopGuard_1 = require("../loopGuard");
const denialTracker_1 = require("./denialTracker");
const stateManager_1 = require("./stateManager");
const callJournal_1 = require("./callJournal");
const sessionCompact_1 = require("../memory/sessionCompact");
const JOURNAL_WINDOW = 400;
/** Pure rollup over journal entries (parsed objects, oldest→newest). */
function summarizeJournal(entries) {
const counts = {};
const errors = new Map();
for (const e of entries) {
if (!e || typeof e.status !== "string")
continue;
counts[e.status] = (counts[e.status] || 0) + 1;
if (e.status === "error" || e.status === "denied") {
const k = `${e.code ?? "?"} ${e.tool ?? "?"}`;
errors.set(k, (errors.get(k) || 0) + 1);
}
}
const topErrors = [...errors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
const lastProblems = entries
.filter((e) => e && ["error", "denied", "loop_blocked", "streak_blocked"].includes(e.status))
.slice(-4)
.reverse();
return { counts, topErrors, lastProblems };
}
/** Pure renderer; every field optional — never throws on missing data. */
function formatStatus(d) {
const j = d.journal || { counts: {}, topErrors: [], lastProblems: [] };
const L = ["## maestro self-status"];
const rt = d.runtime || {};
L.push(`process: node ${rt.node ?? "?"} | uptime ${Math.round((rt.uptimeSec ?? 0) / 60)}m | rss ${Math.round(rt.rssMB ?? 0)}MB`);
L.push(`journal (last ${j.window ?? "?"} calls): ` + (Object.entries(j.counts).map(([k, v]) => `${k}=${v}`).join(" ") || "empty") + (j.sizeKB != null ? ` | file ${j.sizeKB}KB` : ""));
if (j.topErrors?.length)
L.push("top issues: " + j.topErrors.map(([k, v]) => `${k} ×${v}`).join(" · "));
if (j.lastProblems?.length)
L.push("recent: " + j.lastProblems.map((e) => `${e.tool} ${e.status}${e.code ? "/" + e.code : ""} @${String(e.ts || "").slice(11, 19)}`).join(" · "));
const ls = d.loopStats || {};
L.push(`loop-guard: trackers=${ls.exact ?? 0} streaks=${ls.streaks ?? 0} | phase ${JSON.stringify(d.phaseCounts || {})}`);
if (d.denials)
L.push(d.denials);
const cg = d.gauge || {};
L.push(`gauge: lastPct=${cg.lastPct != null ? (cg.lastPct * 100).toFixed(0) + "%" : "n/a"} warned=${cg.warned ? "yes(" + (cg.warnedPct != null ? (cg.warnedPct * 100).toFixed(0) + "%" : "?") + ")" : "no"} | chat turns=${d.messageCount ?? "?"}`);
const q = d.queues || {};
L.push(`queues: pending-facts=${q.pendingFacts ?? 0} | compact-request=${q.compactRequest ? "pending(attempts " + q.compactRequest + ")" : "none"}`);
L.push(`journal path: ${d.journalPath ?? "?"}`);
return L.join("\n");
}
const maestroStatusTool = (0, sdk_1.tool)({
name: "maestro_status",
description: "Read-only self-diagnostics of the maestro plugin: recent tool-call journal rollup (ok/error/blocked counts, top issues), loop-guard tracker state, active denials, context-gauge reading (last known window fill %), pending extraction/compaction queues, process vitals. Use to orient at session start or when something behaves oddly. Never mutates anything.",
parameters: {},
implementation: async () => {
const raw = await fs_1.readFile(callJournal_1.callJournal.path, "utf8").catch(() => "");
const lines = raw.trim().split("\n").filter(Boolean);
const window = lines.slice(-JOURNAL_WINDOW);
const entries = [];
for (const l of window) {
try {
entries.push(JSON.parse(l));
}
catch { /* skip corrupt */ }
}
const journal = summarizeJournal(entries);
journal.window = window.length;
journal.sizeKB = raw ? Math.round(raw.length / 100) / 10 : 0;
let pendingFacts = 0;
try {
const pf = await fs_1.readFile(path_1.join(os_1.homedir(), ".lmstudio", "plugin-data", "persistent-memory", "pending-facts.jsonl"), "utf8");
pendingFacts = pf.trim().split("\n").filter(Boolean).length;
}
catch { }
const compact = await sessionCompact_1.peekCompactRequest().catch(() => null);
let state = null;
try {
state = await stateManager_1.getPersistedState();
}
catch { }
return formatStatus({
runtime: { node: process.version, uptimeSec: process.uptime(), rssMB: process.memoryUsage().rss / 1048576 },
journal,
journalPath: callJournal_1.callJournal.path,
loopStats: (0, loopGuard_1.loopGuardStats)(),
phaseCounts: { ...loopGuard_1.phaseToolCounts },
denials: denialTracker_1.denialTracker.getSummary(),
gauge: state?.contextGauge,
messageCount: state?.messageCount,
queues: { pendingFacts, compactRequest: compact ? compact.attempts : null },
});
},
});
exports.maestroStatusTool = maestroStatusTool;
//# sourceMappingURL=diagnostics.js.map
"use strict";
/**
* @file diagnostics.ts
* B2: maestro_status — read-only self-diagnostics for the agent:
* tool-call journal rollup, loop-guard counters, denial state,
* context-gauge reading, queue depths, process vitals.
* Pure parts (summarizeJournal/formatStatus) are unit-tested;
* the tool only gathers live data and renders.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.summarizeJournal = summarizeJournal;
exports.formatStatus = formatStatus;
exports.maestroStatusTool = void 0;
const fs_1 = require("fs/promises");
const path_1 = require("path");
const os_1 = require("os");
const sdk_1 = require("@lmstudio/sdk");
const loopGuard_1 = require("../loopGuard");
const denialTracker_1 = require("./denialTracker");
const stateManager_1 = require("./stateManager");
const callJournal_1 = require("./callJournal");
const sessionCompact_1 = require("../memory/sessionCompact");
const JOURNAL_WINDOW = 400;
/** Pure rollup over journal entries (parsed objects, oldest→newest). */
function summarizeJournal(entries) {
const counts = {};
const errors = new Map();
for (const e of entries) {
if (!e || typeof e.status !== "string")
continue;
counts[e.status] = (counts[e.status] || 0) + 1;
if (e.status === "error" || e.status === "denied") {
const k = `${e.code ?? "?"} ${e.tool ?? "?"}`;
errors.set(k, (errors.get(k) || 0) + 1);
}
}
const topErrors = [...errors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
const lastProblems = entries
.filter((e) => e && ["error", "denied", "loop_blocked", "streak_blocked"].includes(e.status))
.slice(-4)
.reverse();
return { counts, topErrors, lastProblems };
}
/** Pure renderer; every field optional — never throws on missing data. */
function formatStatus(d) {
const j = d.journal || { counts: {}, topErrors: [], lastProblems: [] };
const L = ["## maestro self-status"];
const rt = d.runtime || {};
L.push(`process: node ${rt.node ?? "?"} | uptime ${Math.round((rt.uptimeSec ?? 0) / 60)}m | rss ${Math.round(rt.rssMB ?? 0)}MB`);
L.push(`journal (last ${j.window ?? "?"} calls): ` + (Object.entries(j.counts).map(([k, v]) => `${k}=${v}`).join(" ") || "empty") + (j.sizeKB != null ? ` | file ${j.sizeKB}KB` : ""));
if (j.topErrors?.length)
L.push("top issues: " + j.topErrors.map(([k, v]) => `${k} ×${v}`).join(" · "));
if (j.lastProblems?.length)
L.push("recent: " + j.lastProblems.map((e) => `${e.tool} ${e.status}${e.code ? "/" + e.code : ""} @${String(e.ts || "").slice(11, 19)}`).join(" · "));
const ls = d.loopStats || {};
L.push(`loop-guard: trackers=${ls.exact ?? 0} streaks=${ls.streaks ?? 0} | phase ${JSON.stringify(d.phaseCounts || {})}`);
if (d.denials)
L.push(d.denials);
const cg = d.gauge || {};
L.push(`gauge: lastPct=${cg.lastPct != null ? (cg.lastPct * 100).toFixed(0) + "%" : "n/a"} warned=${cg.warned ? "yes(" + (cg.warnedPct != null ? (cg.warnedPct * 100).toFixed(0) + "%" : "?") + ")" : "no"} | chat turns=${d.messageCount ?? "?"}`);
const q = d.queues || {};
L.push(`queues: pending-facts=${q.pendingFacts ?? 0} | compact-request=${q.compactRequest ? "pending(attempts " + q.compactRequest + ")" : "none"}`);
L.push(`journal path: ${d.journalPath ?? "?"}`);
return L.join("\n");
}
const maestroStatusTool = (0, sdk_1.tool)({
name: "maestro_status",
description: "Read-only self-diagnostics of the maestro plugin: recent tool-call journal rollup (ok/error/blocked counts, top issues), loop-guard tracker state, active denials, context-gauge reading (last known window fill %), pending extraction/compaction queues, process vitals. Use to orient at session start or when something behaves oddly. Never mutates anything.",
parameters: {},
implementation: async () => {
const raw = await fs_1.readFile(callJournal_1.callJournal.path, "utf8").catch(() => "");
const lines = raw.trim().split("\n").filter(Boolean);
const window = lines.slice(-JOURNAL_WINDOW);
const entries = [];
for (const l of window) {
try {
entries.push(JSON.parse(l));
}
catch { /* skip corrupt */ }
}
const journal = summarizeJournal(entries);
journal.window = window.length;
journal.sizeKB = raw ? Math.round(raw.length / 100) / 10 : 0;
let pendingFacts = 0;
try {
const pf = await fs_1.readFile(path_1.join(os_1.homedir(), ".lmstudio", "plugin-data", "persistent-memory", "pending-facts.jsonl"), "utf8");
pendingFacts = pf.trim().split("\n").filter(Boolean).length;
}
catch { }
const compact = await sessionCompact_1.peekCompactRequest().catch(() => null);
let state = null;
try {
state = await stateManager_1.getPersistedState();
}
catch { }
return formatStatus({
runtime: { node: process.version, uptimeSec: process.uptime(), rssMB: process.memoryUsage().rss / 1048576 },
journal,
journalPath: callJournal_1.callJournal.path,
loopStats: (0, loopGuard_1.loopGuardStats)(),
phaseCounts: { ...loopGuard_1.phaseToolCounts },
denials: denialTracker_1.denialTracker.getSummary(),
gauge: state?.contextGauge,
messageCount: state?.messageCount,
queues: { pendingFacts, compactRequest: compact ? compact.attempts : null },
});
},
});
exports.maestroStatusTool = maestroStatusTool;
//# sourceMappingURL=diagnostics.js.map