feat(headless): expose memory extraction diagnostics

This commit is contained in:
Mikael Hugo 2026-05-15 18:13:35 +02:00
parent f00762ffdb
commit 90b8e7edf8
2 changed files with 74 additions and 0 deletions

View file

@ -183,6 +183,17 @@ export interface QuerySnapshot {
// section itself so this is an estimate, not a meter.
estimated_total_tokens: number;
};
memoryExtraction?: {
total_attempts: number;
recent_attempts: Array<{
unit_key: string;
status: string;
failure_class: string | null;
reason: string | null;
error: string | null;
created_at: string;
}>;
};
schedule?: {
pending_count: number;
overdue_count: number;
@ -413,6 +424,36 @@ export async function buildQuerySnapshot(
// runtime_counters unavailable on legacy DBs — fine, drop the section.
}
let memoryExtraction: QuerySnapshot["memoryExtraction"];
try {
const memoryDbModule = (await jiti.import(
sfExtensionPath("sf-db/sf-db-memory"),
{},
)) as { listMemoryExtractionAttempts: (limit?: number) => any[] };
const sfDbModule = (await jiti.import(sfExtensionPath("sf-db"), {})) as {
_getAdapter: () => any;
};
const recent = memoryDbModule.listMemoryExtractionAttempts(5);
const adapter = sfDbModule._getAdapter();
const total =
adapter
?.prepare("SELECT count(*) AS cnt FROM memory_extraction_attempts")
.get()?.cnt ?? recent.length;
memoryExtraction = {
total_attempts: Number(total),
recent_attempts: recent.map((row) => ({
unit_key: String(row.unit_key ?? ""),
status: String(row.status ?? ""),
failure_class: row.failure_class ? String(row.failure_class) : null,
reason: row.reason ? String(row.reason) : null,
error: row.error ? String(row.error) : null,
created_at: String(row.created_at ?? ""),
})),
};
} catch {
// Older DBs may not have the extraction attempt table yet.
}
const snapshot: QuerySnapshot = {
schemaVersion: 1,
state,
@ -428,6 +469,7 @@ export async function buildQuerySnapshot(
},
uokDiagnostics,
...(memoryInjection ? { memoryInjection } : {}),
...(memoryExtraction ? { memoryExtraction } : {}),
schedule: scheduleEntries,
};

View file

@ -0,0 +1,32 @@
/**
* Smoke test for memoryExtraction diagnostics in headless-query output.
*
* Source-level guard: the query snapshot exposes recent extraction attempts
* and their failure_class so operators can inspect memory closeout health
* without sqlite3 or interactive /memory status.
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { test } from "vitest";
const __dirname = dirname(fileURLToPath(import.meta.url));
const querySrc = readFileSync(
join(__dirname, "..", "headless-query.ts"),
"utf-8",
);
test("QuerySnapshot type declares memoryExtraction section", () => {
assert.match(querySrc, /memoryExtraction\?:/);
assert.match(querySrc, /total_attempts:\s*number/);
assert.match(querySrc, /recent_attempts:/);
assert.match(querySrc, /failure_class:\s*string \| null/);
});
test("buildQuerySnapshot reads memory extraction attempts", () => {
assert.match(querySrc, /listMemoryExtractionAttempts\(5\)/);
assert.match(querySrc, /memory_extraction_attempts/);
assert.match(querySrc, /failure_class/);
});