refactor: read memory dreaming status from sqlite

This commit is contained in:
Peter Steinberger
2026-05-09 05:03:11 +01:00
parent 034c287429
commit d8ba576d73
10 changed files with 163 additions and 268 deletions

View File

@@ -373,6 +373,9 @@ The remaining cleanup is mostly consolidation and deletion:
filesystem existence checks. Its transcript-ingestion tests seed SQLite rows
through neutral test locators instead of creating `agents/<id>/sessions`
fixtures.
- Gateway doctor memory status reads short-term recall and phase-signal counts
from SQLite plugin-state rows instead of `memory/.dreams/*.json`; CLI and
doctor output now label that storage as a SQLite store, not a path.
- Sandbox container/browser registries now use the shared
`sandbox_registry_entries` SQLite table. Doctor imports legacy monolithic and
sharded JSON registry files and removes successful sources.

View File

@@ -54,7 +54,7 @@ import {
recordGroundedShortTermCandidates,
recordShortTermRecalls,
rankShortTermPromotionCandidates,
resolveShortTermRecallStorePath,
resolveShortTermRecallStoreLabel,
type RepairShortTermPromotionArtifactsResult,
type ShortTermAuditSummary,
} from "./short-term-promotion.js";
@@ -926,7 +926,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
}
if (audit) {
lines.push(`${label("Recall store")} ${info(formatAuditCounts(audit))}`);
lines.push(`${label("Recall path")} ${info(shortenHomePath(audit.storePath))}`);
lines.push(`${label("Recall storage")} ${info(audit.storeLabel)}`);
if (audit.updatedAt) {
lines.push(`${label("Recall updated")} ${info(audit.updatedAt)}`);
}
@@ -1275,7 +1275,7 @@ export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) {
}
}
const storePath = resolveShortTermRecallStorePath(workspaceDir);
const storeLabel = resolveShortTermRecallStoreLabel(workspaceDir);
const customQmd = asRecord(asRecord(status.custom)?.qmd);
const audit = await auditShortTermPromotionArtifacts({
workspaceDir,
@@ -1292,7 +1292,7 @@ export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) {
if (opts.json) {
defaultRuntime.writeJson({
workspaceDir,
storePath,
storeLabel,
audit,
candidates,
apply: applyResult
@@ -1310,7 +1310,7 @@ export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) {
if (candidates.length === 0) {
defaultRuntime.log("No short-term recall candidates.");
defaultRuntime.log(`Recall store: ${shortenHomePath(storePath)}`);
defaultRuntime.log(`Recall store: ${storeLabel}`);
if (audit.issues.length > 0) {
for (const issue of audit.issues) {
defaultRuntime.log(issue.message);
@@ -1328,7 +1328,7 @@ export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) {
`(${agentId})`,
)}`,
);
lines.push(`${colorize(rich, theme.muted, "Recall store:")} ${shortenHomePath(storePath)}`);
lines.push(`${colorize(rich, theme.muted, "Recall store:")} ${storeLabel}`);
lines.push(colorize(rich, theme.muted, `Store health: ${formatAuditCounts(audit)}`));
for (const candidate of candidates) {
lines.push(
@@ -1726,7 +1726,7 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) {
: {}),
...(shortTermRollback
? {
shortTermStorePath: shortTermRollback.storePath,
shortTermStoreLabel: shortTermRollback.storeLabel,
removedShortTermEntries: shortTermRollback.removed,
}
: {}),
@@ -1752,7 +1752,7 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) {
colorize(
isRich(),
theme.muted,
`shortTermStorePath=${shortenHomePath(shortTermRollback.storePath)}`,
`shortTermStoreLabel=${shortTermRollback.storeLabel}`,
),
colorize(
isRich(),

View File

@@ -1584,7 +1584,7 @@ describe("short-term promotion", () => {
});
const audit = await auditShortTermPromotionArtifacts({ workspaceDir });
expect(audit.storePath).toBe(
expect(audit.storeLabel).toBe(
"sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
);
expect(audit.invalidEntryCount).toBe(0);

View File

@@ -153,7 +153,7 @@ type ShortTermAuditIssue = {
};
export type ShortTermAuditSummary = {
storePath: string;
storeLabel: string;
updatedAt?: string;
exists: boolean;
entryCount: number;
@@ -1626,12 +1626,12 @@ export async function applyShortTermPromotions(
});
}
export function resolveShortTermRecallStorePath(workspaceDir: string): string {
export function resolveShortTermRecallStoreLabel(workspaceDir: string): string {
void workspaceDir;
return resolveSqliteStoreLabel(MEMORY_CORE_SHORT_TERM_RECALL_NAMESPACE);
}
export function resolveShortTermPhaseSignalStorePath(workspaceDir: string): string {
export function resolveShortTermPhaseSignalStoreLabel(workspaceDir: string): string {
void workspaceDir;
return resolveSqliteStoreLabel(MEMORY_CORE_SHORT_TERM_PHASE_SIGNAL_NAMESPACE);
}
@@ -1644,7 +1644,7 @@ export async function auditShortTermPromotionArtifacts(params: {
};
}): Promise<ShortTermAuditSummary> {
const workspaceDir = params.workspaceDir.trim();
const storePath = resolveShortTermRecallStorePath(workspaceDir);
const storeLabel = resolveShortTermRecallStoreLabel(workspaceDir);
const issues: ShortTermAuditIssue[] = [];
const nowIso = new Date().toISOString();
const store = await readStore(workspaceDir, nowIso);
@@ -1711,7 +1711,7 @@ export async function auditShortTermPromotionArtifacts(params: {
}
return {
storePath,
storeLabel,
updatedAt,
exists,
entryCount,
@@ -1778,9 +1778,9 @@ export async function repairShortTermPromotionArtifacts(params: {
export async function removeGroundedShortTermCandidates(params: {
workspaceDir: string;
}): Promise<{ removed: number; storePath: string }> {
}): Promise<{ removed: number; storeLabel: string }> {
const workspaceDir = params.workspaceDir.trim();
const storePath = resolveShortTermRecallStorePath(workspaceDir);
const storeLabel = resolveShortTermRecallStoreLabel(workspaceDir);
const nowIso = new Date().toISOString();
let removed = 0;
@@ -1817,7 +1817,7 @@ export async function removeGroundedShortTermCandidates(params: {
}
});
return { removed, storePath };
return { removed, storeLabel };
}
export const __testing = {

View File

@@ -101,7 +101,7 @@ import { detectLegacyWorkspaceDirs, formatRootMemoryFilesWarning } from "./docto
function resetMemoryRecallMocks() {
auditShortTermPromotionArtifacts.mockReset();
auditShortTermPromotionArtifacts.mockResolvedValue({
storePath: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
storeLabel: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
exists: true,
entryCount: 1,
promotedCount: 0,
@@ -735,7 +735,7 @@ describe("memory recall doctor integration", () => {
it("notes recall-store audit problems with doctor guidance", async () => {
auditShortTermPromotionArtifacts.mockResolvedValueOnce({
storePath: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
storeLabel: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
exists: true,
entryCount: 12,
promotedCount: 4,
@@ -767,7 +767,7 @@ describe("memory recall doctor integration", () => {
it("runs memory recall repair during doctor --fix", async () => {
auditShortTermPromotionArtifacts.mockResolvedValueOnce({
storePath: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
storeLabel: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
exists: true,
entryCount: 12,
promotedCount: 4,

View File

@@ -173,7 +173,7 @@ function buildMemoryRecallIssueNote(audit: ShortTermAuditSummary): string | null
return [
"Memory recall artifacts need attention:",
...issueLines,
`Recall store: ${audit.storePath}`,
`Recall store: ${audit.storeLabel}`,
guidance,
].join("\n");
}

View File

@@ -22,6 +22,7 @@ const writeBackfillDiaryEntries = vi.hoisted(() => vi.fn());
const removeBackfillDiaryEntries = vi.hoisted(() => vi.fn());
const removeGroundedShortTermCandidates = vi.hoisted(() => vi.fn());
const repairDreamingArtifacts = vi.hoisted(() => vi.fn());
const readDreamingWorkspaceMap = vi.hoisted(() => vi.fn(async () => ({})));
vi.mock("../../config/config.js", () => ({
getRuntimeConfig,
@@ -50,8 +51,21 @@ vi.mock("./doctor.memory-core-runtime.js", () => ({
repairDreamingArtifacts,
}));
vi.mock("../../memory-host-sdk/dreaming-state-store.js", async () => {
const actual = await vi.importActual<
typeof import("../../memory-host-sdk/dreaming-state-store.js")
>("../../memory-host-sdk/dreaming-state-store.js");
return {
...actual,
readDreamingWorkspaceMap,
};
});
import { doctorHandlers } from "./doctor.js";
const SHORT_TERM_RECALL_NAMESPACE = "dreaming.short-term-recall";
const SHORT_TERM_PHASE_SIGNAL_NAMESPACE = "dreaming.phase-signals";
const makeRuntimeContext = () => ({ getRuntimeConfig: () => getRuntimeConfig() });
const invokeDoctorMemoryStatus = async (
@@ -214,6 +228,7 @@ describe("doctor.memory.status", () => {
removeBackfillDiaryEntries.mockReset();
removeGroundedShortTermCandidates.mockReset();
repairDreamingArtifacts.mockReset();
readDreamingWorkspaceMap.mockReset().mockResolvedValue({});
});
it("returns gateway embedding probe status for the default agent", async () => {
@@ -344,141 +359,77 @@ describe("doctor.memory.status", () => {
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "doctor-memory-status-"));
const mainWorkspaceDir = path.join(workspaceRoot, "main");
const alphaWorkspaceDir = path.join(workspaceRoot, "alpha");
const mainStorePath = path.join(
mainWorkspaceDir,
"memory",
".dreams",
"short-term-recall.json",
);
const alphaStorePath = path.join(
alphaWorkspaceDir,
"memory",
".dreams",
"short-term-recall.json",
);
const mainPhaseSignalPath = path.join(
mainWorkspaceDir,
"memory",
".dreams",
"phase-signals.json",
);
const alphaPhaseSignalPath = path.join(
alphaWorkspaceDir,
"memory",
".dreams",
"phase-signals.json",
);
await fs.mkdir(path.dirname(mainStorePath), { recursive: true });
await fs.mkdir(path.dirname(alphaStorePath), { recursive: true });
await fs.writeFile(
mainStorePath,
`${JSON.stringify(
{
version: 1,
updatedAt: recentIso,
entries: {
"memory:memory/2026-04-03.md:1:2": {
path: "memory/2026-04-03.md",
startLine: 1,
endLine: 2,
snippet: "Emma prefers shorter, lower-pressure check-ins.",
source: "memory",
recallCount: 2,
dailyCount: 1,
lastRecalledAt: recentIso,
promotedAt: undefined,
},
"memory:memory/2026-04-02.md:1:2": {
path: "memory/2026-04-02.md",
startLine: 1,
endLine: 2,
snippet: "Use the Happy Together calendar for flights.",
source: "memory",
recallCount: 9,
dailyCount: 5,
promotedAt: recentIso,
},
readDreamingWorkspaceMap.mockImplementation(async (namespace: string, workspaceDir: string) => {
if (namespace === SHORT_TERM_RECALL_NAMESPACE && workspaceDir === mainWorkspaceDir) {
return {
"memory:memory/2026-04-03.md:1:2": {
path: "memory/2026-04-03.md",
startLine: 1,
endLine: 2,
snippet: "Emma prefers shorter, lower-pressure check-ins.",
source: "memory",
recallCount: 2,
dailyCount: 1,
lastRecalledAt: recentIso,
},
},
null,
2,
)}\n`,
"utf-8",
);
await fs.writeFile(
alphaStorePath,
`${JSON.stringify(
{
version: 1,
updatedAt: recentIso,
entries: {
"memory:memory/2026-04-01.md:1:2": {
path: "memory/2026-04-01.md",
startLine: 1,
endLine: 2,
snippet: "Bunji lives in London.",
source: "memory",
recallCount: 7,
dailyCount: 4,
promotedAt: olderIso,
},
"memory:memory/2026-04-04.md:1:2": {
path: "memory/2026-04-04.md",
startLine: 1,
endLine: 2,
snippet: "Always book the covered valet option at Park & Greet BCN.",
source: "memory",
recallCount: 8,
dailyCount: 3,
promotedAt: recentIso,
},
"memory:memory/2026-04-02.md:1:2": {
path: "memory/2026-04-02.md",
startLine: 1,
endLine: 2,
snippet: "Use the Happy Together calendar for flights.",
source: "memory",
recallCount: 9,
dailyCount: 5,
promotedAt: recentIso,
},
},
null,
2,
)}\n`,
"utf-8",
);
await fs.writeFile(
mainPhaseSignalPath,
`${JSON.stringify(
{
version: 1,
updatedAt: recentIso,
entries: {
"memory:memory/2026-04-03.md:1:2": {
lightHits: 2,
remHits: 3,
},
"memory:memory/2026-04-02.md:1:2": {
lightHits: 9,
remHits: 9,
},
};
}
if (namespace === SHORT_TERM_RECALL_NAMESPACE && workspaceDir === alphaWorkspaceDir) {
return {
"memory:memory/2026-04-01.md:1:2": {
path: "memory/2026-04-01.md",
startLine: 1,
endLine: 2,
snippet: "Bunji lives in London.",
source: "memory",
recallCount: 7,
dailyCount: 4,
promotedAt: olderIso,
},
},
null,
2,
)}\n`,
"utf-8",
);
await fs.writeFile(
alphaPhaseSignalPath,
`${JSON.stringify(
{
version: 1,
updatedAt: recentIso,
entries: {
"memory:memory/2026-04-01.md:1:2": {
lightHits: 5,
remHits: 5,
},
"memory:memory/2026-04-04.md:1:2": {
path: "memory/2026-04-04.md",
startLine: 1,
endLine: 2,
snippet: "Always book the covered valet option at Park & Greet BCN.",
source: "memory",
recallCount: 8,
dailyCount: 3,
promotedAt: recentIso,
},
},
null,
2,
)}\n`,
"utf-8",
);
};
}
if (namespace === SHORT_TERM_PHASE_SIGNAL_NAMESPACE && workspaceDir === mainWorkspaceDir) {
return {
"memory:memory/2026-04-03.md:1:2": {
lightHits: 2,
remHits: 3,
},
"memory:memory/2026-04-02.md:1:2": {
lightHits: 9,
remHits: 9,
},
};
}
if (namespace === SHORT_TERM_PHASE_SIGNAL_NAMESPACE && workspaceDir === alphaWorkspaceDir) {
return {
"memory:memory/2026-04-01.md:1:2": {
lightHits: 5,
remHits: 5,
},
};
}
return {};
});
getRuntimeConfig.mockReturnValue({
agents: {
@@ -607,27 +558,18 @@ describe("doctor.memory.status", () => {
it("falls back to the manager workspace when no configured dreaming workspaces resolve", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "doctor-memory-fallback-"));
const storePath = path.join(workspaceDir, "memory", ".dreams", "short-term-recall.json");
await fs.mkdir(path.dirname(storePath), { recursive: true });
await fs.writeFile(
storePath,
`${JSON.stringify(
{
version: 1,
updatedAt: "2026-04-04T00:00:00.000Z",
entries: {
"memory:memory/2026-04-03.md:1:2": {
path: "memory/2026-04-03.md",
source: "memory",
promotedAt: "2026-04-04T00:00:00.000Z",
},
},
readDreamingWorkspaceMap.mockImplementation(async (namespace: string, candidate: string) => {
if (namespace !== SHORT_TERM_RECALL_NAMESPACE || candidate !== workspaceDir) {
return {};
}
return {
"memory:memory/2026-04-03.md:1:2": {
path: "memory/2026-04-03.md",
source: "memory",
promotedAt: "2026-04-04T00:00:00.000Z",
},
null,
2,
)}\n`,
"utf-8",
);
};
});
resolveMemorySearchConfig.mockReturnValue(null);
getRuntimeConfig.mockReturnValue({
plugins: {
@@ -720,27 +662,12 @@ describe("doctor.memory.status", () => {
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "doctor-memory-error-"));
const mainWorkspaceDir = path.join(workspaceRoot, "main");
const alphaWorkspaceDir = path.join(workspaceRoot, "alpha");
const alphaStorePath = path.join(
alphaWorkspaceDir,
"memory",
".dreams",
"short-term-recall.json",
);
await fs.mkdir(path.dirname(alphaStorePath), { recursive: true });
await fs.writeFile(
alphaStorePath,
`${JSON.stringify(
{
version: 1,
updatedAt: "2026-04-04T00:00:00.000Z",
entries: {},
},
null,
2,
)}\n`,
"utf-8",
);
await fs.mkdir(path.join(mainWorkspaceDir, "memory", ".dreams"), { recursive: true });
readDreamingWorkspaceMap.mockImplementation(async (namespace: string) => {
if (namespace === SHORT_TERM_RECALL_NAMESPACE) {
throw Object.assign(new Error("denied"), { code: "EACCES" });
}
return {};
});
getRuntimeConfig.mockReturnValue({
agents: {
@@ -768,27 +695,6 @@ describe("doctor.memory.status", () => {
agentId === "alpha" ? alphaWorkspaceDir : mainWorkspaceDir,
);
const readFileSpy = vi.spyOn(fs, "readFile").mockImplementation(async (target, options) => {
const targetPath =
typeof target === "string"
? target
: Buffer.isBuffer(target)
? target.toString("utf-8")
: target instanceof URL
? target.pathname
: "";
if (
targetPath === path.join(mainWorkspaceDir, "memory", ".dreams", "short-term-recall.json") ||
targetPath === alphaStorePath
) {
const error = Object.assign(new Error("denied"), { code: "EACCES" });
throw error;
}
return await vi
.importActual<typeof import("node:fs/promises")>("node:fs/promises")
.then((actual) => actual.readFile(target, options as never));
});
const close = vi.fn().mockResolvedValue(undefined);
getMemorySearchManager.mockResolvedValue({
manager: {
@@ -808,7 +714,6 @@ describe("doctor.memory.status", () => {
storeError: "2 dreaming stores had read errors.",
});
} finally {
readFileSpy.mockRestore();
await fs.rm(workspaceRoot, { recursive: true, force: true });
}
});
@@ -819,7 +724,7 @@ describe("doctor.memory dream actions", () => {
resolveAgentWorkspaceDir.mockReturnValue("/tmp/openclaw");
removeGroundedShortTermCandidates.mockResolvedValue({
removed: 3,
storePath: "/tmp/openclaw/memory/.dreams/short-term-recall.json",
storeLabel: "sqlite:plugin_state_entries/memory-core/dreaming.short-term-recall",
});
const respond = vi.fn();

View File

@@ -2,6 +2,11 @@ import fs from "node:fs/promises";
import path from "node:path";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import {
MEMORY_CORE_SHORT_TERM_PHASE_SIGNAL_NAMESPACE,
MEMORY_CORE_SHORT_TERM_RECALL_NAMESPACE,
readDreamingWorkspaceMap,
} from "../../memory-host-sdk/dreaming-state-store.js";
import {
isSameMemoryDreamingDay,
resolveMemoryDeepDreamingConfig,
@@ -25,8 +30,6 @@ import {
import { asRecord, normalizeTrimmedString } from "./record-shared.js";
import type { GatewayRequestHandlers } from "./types.js";
const SHORT_TERM_STORE_RELATIVE_PATH = path.join("memory", ".dreams", "short-term-recall.json");
const SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH = path.join("memory", ".dreams", "phase-signals.json");
const MANAGED_DEEP_SLEEP_CRON_NAME = "Memory Dreaming Promotion";
const MANAGED_DEEP_SLEEP_CRON_TAG = "[managed-by=memory-core.short-term-promotion]";
const DEEP_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_short_term_promotion_dream__";
@@ -96,8 +99,8 @@ type DoctorMemoryDreamingPayload = {
remPhaseHitCount: number;
promotedTotal: number;
promotedToday: number;
storePath?: string;
phaseSignalPath?: string;
storeLabel?: string;
phaseSignalLabel?: string;
lastPromotedAt?: string;
storeError?: string;
phaseSignalError?: string;
@@ -269,8 +272,8 @@ function resolveDreamingConfig(
| "remPhaseHitCount"
| "promotedTotal"
| "promotedToday"
| "storePath"
| "phaseSignalPath"
| "storeLabel"
| "phaseSignalLabel"
| "lastPromotedAt"
| "storeError"
| "phaseSignalError"
@@ -371,8 +374,8 @@ type DreamingStoreStats = Pick<
| "remPhaseHitCount"
| "promotedTotal"
| "promotedToday"
| "storePath"
| "phaseSignalPath"
| "storeLabel"
| "phaseSignalLabel"
| "lastPromotedAt"
| "storeError"
| "phaseSignalError"
@@ -480,18 +483,22 @@ function trimDreamingEntries(
return selected;
}
function resolveDreamingStoreLabel(namespace: string): string {
return `sqlite:plugin_state_entries/memory-core/${namespace}`;
}
async function loadDreamingStoreStats(
workspaceDir: string,
nowMs: number,
timezone?: string,
): Promise<DreamingStoreStats> {
const storePath = path.join(workspaceDir, SHORT_TERM_STORE_RELATIVE_PATH);
const phaseSignalPath = path.join(workspaceDir, SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH);
const storeLabel = resolveDreamingStoreLabel(MEMORY_CORE_SHORT_TERM_RECALL_NAMESPACE);
const phaseSignalLabel = resolveDreamingStoreLabel(MEMORY_CORE_SHORT_TERM_PHASE_SIGNAL_NAMESPACE);
try {
const raw = await fs.readFile(storePath, "utf-8");
const parsed = JSON.parse(raw) as unknown;
const store = asRecord(parsed);
const entries = asRecord(store?.entries) ?? {};
const entries = await readDreamingWorkspaceMap<unknown>(
MEMORY_CORE_SHORT_TERM_RECALL_NAMESPACE,
workspaceDir,
);
let shortTermCount = 0;
let recallSignalCount = 0;
let dailySignalCount = 0;
@@ -574,10 +581,10 @@ async function loadDreamingStoreStats(
let phaseSignalError: string | undefined;
try {
const phaseRaw = await fs.readFile(phaseSignalPath, "utf-8");
const parsedPhase = JSON.parse(phaseRaw) as unknown;
const phaseStore = asRecord(parsedPhase);
const phaseEntries = asRecord(phaseStore?.entries) ?? {};
const phaseEntries = await readDreamingWorkspaceMap<unknown>(
MEMORY_CORE_SHORT_TERM_PHASE_SIGNAL_NAMESPACE,
workspaceDir,
);
for (const [key, value] of Object.entries(phaseEntries)) {
if (!activeKeys.has(key)) {
continue;
@@ -613,8 +620,8 @@ async function loadDreamingStoreStats(
remPhaseHitCount,
promotedTotal,
promotedToday,
storePath,
phaseSignalPath,
storeLabel,
phaseSignalLabel,
shortTermEntries: trimDreamingEntries(shortTermEntries, compareDreamingEntryByRecency),
signalEntries: trimDreamingEntries(shortTermEntries, compareDreamingEntryBySignals),
promotedEntries: trimDreamingEntries(promotedEntries, compareDreamingEntryByPromotion),
@@ -622,26 +629,6 @@ async function loadDreamingStoreStats(
...(phaseSignalError ? { phaseSignalError } : {}),
};
} catch (err) {
const code = (err as NodeJS.ErrnoException | undefined)?.code;
if (code === "ENOENT") {
return {
shortTermCount: 0,
recallSignalCount: 0,
dailySignalCount: 0,
groundedSignalCount: 0,
totalSignalCount: 0,
phaseSignalCount: 0,
lightPhaseHitCount: 0,
remPhaseHitCount: 0,
promotedTotal: 0,
promotedToday: 0,
storePath,
phaseSignalPath,
shortTermEntries: [],
signalEntries: [],
promotedEntries: [],
};
}
return {
shortTermCount: 0,
recallSignalCount: 0,
@@ -653,8 +640,8 @@ async function loadDreamingStoreStats(
remPhaseHitCount: 0,
promotedTotal: 0,
promotedToday: 0,
storePath,
phaseSignalPath,
storeLabel,
phaseSignalLabel,
shortTermEntries: [],
signalEntries: [],
promotedEntries: [],
@@ -676,8 +663,8 @@ function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStat
let promotedToday = 0;
let latestPromotedAtMs = Number.NEGATIVE_INFINITY;
let lastPromotedAt: string | undefined;
const storePaths = new Set<string>();
const phaseSignalPaths = new Set<string>();
const storeLabels = new Set<string>();
const phaseSignalLabels = new Set<string>();
const storeErrors: string[] = [];
const phaseSignalErrors: string[] = [];
const shortTermEntries: DoctorMemoryDreamingEntryPayload[] = [];
@@ -695,11 +682,11 @@ function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStat
remPhaseHitCount += stat.remPhaseHitCount;
promotedTotal += stat.promotedTotal;
promotedToday += stat.promotedToday;
if (stat.storePath) {
storePaths.add(stat.storePath);
if (stat.storeLabel) {
storeLabels.add(stat.storeLabel);
}
if (stat.phaseSignalPath) {
phaseSignalPaths.add(stat.phaseSignalPath);
if (stat.phaseSignalLabel) {
phaseSignalLabels.add(stat.phaseSignalLabel);
}
if (stat.storeError) {
storeErrors.push(stat.storeError);
@@ -731,8 +718,8 @@ function mergeDreamingStoreStats(stats: DreamingStoreStats[]): DreamingStoreStat
shortTermEntries: trimDreamingEntries(shortTermEntries, compareDreamingEntryByRecency),
signalEntries: trimDreamingEntries(signalEntries, compareDreamingEntryBySignals),
promotedEntries: trimDreamingEntries(promotedEntries, compareDreamingEntryByPromotion),
...(storePaths.size === 1 ? { storePath: [...storePaths][0] } : {}),
...(phaseSignalPaths.size === 1 ? { phaseSignalPath: [...phaseSignalPaths][0] } : {}),
...(storeLabels.size === 1 ? { storeLabel: [...storeLabels][0] } : {}),
...(phaseSignalLabels.size === 1 ? { phaseSignalLabel: [...phaseSignalLabels][0] } : {}),
...(lastPromotedAt ? { lastPromotedAt } : {}),
...(storeErrors.length === 1
? { storeError: storeErrors[0] }

View File

@@ -28,7 +28,7 @@ type RuntimeFacadeModule = {
}) => void;
removeGroundedShortTermCandidates: (params: {
workspaceDir: string;
}) => Promise<{ removed: number; storePath: string }>;
}) => Promise<{ removed: number; storeLabel: string }>;
repairDreamingArtifacts: (params: {
workspaceDir: string;
archiveDiary?: boolean;

View File

@@ -63,7 +63,7 @@ export type ShortTermAuditIssue = {
};
export type ShortTermAuditSummary = {
storePath: string;
storeLabel: string;
updatedAt?: string;
exists: boolean;
entryCount: number;