mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-16 10:28:45 +00:00
refactor: read memory dreaming status from sqlite
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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] }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -63,7 +63,7 @@ export type ShortTermAuditIssue = {
|
||||
};
|
||||
|
||||
export type ShortTermAuditSummary = {
|
||||
storePath: string;
|
||||
storeLabel: string;
|
||||
updatedAt?: string;
|
||||
exists: boolean;
|
||||
entryCount: number;
|
||||
|
||||
Reference in New Issue
Block a user