mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 23:40:43 +00:00
memory: tighten dreaming bot triage fixes
This commit is contained in:
@@ -713,8 +713,8 @@ describe("memory-core dreaming phases", () => {
|
||||
expect(Object.keys(sessionIngestion.files)).toHaveLength(1);
|
||||
expect(Object.values(sessionIngestion.files)).toEqual([
|
||||
expect.objectContaining({
|
||||
lineCount: 2,
|
||||
lastContentLine: 2,
|
||||
lineCount: 0,
|
||||
lastContentLine: 0,
|
||||
contentHash: expect.any(String),
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -230,6 +230,38 @@ describe("short-term promotion", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores bullet-prefixed dreaming snippets when recording short-term recalls", async () => {
|
||||
await withTempWorkspace(async (workspaceDir) => {
|
||||
await recordShortTermRecalls({
|
||||
workspaceDir,
|
||||
query: "action preference",
|
||||
results: [
|
||||
{
|
||||
path: "memory/2026-04-03.md",
|
||||
source: "memory",
|
||||
startLine: 1,
|
||||
endLine: 5,
|
||||
score: 0.92,
|
||||
snippet: [
|
||||
"- Candidate: Default to action.",
|
||||
" - confidence: 0.76",
|
||||
" - evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1",
|
||||
" - recalls: 3",
|
||||
" - status: staged",
|
||||
].join("\n"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
JSON.parse(await fs.readFile(resolveShortTermRecallStorePath(workspaceDir), "utf-8")),
|
||||
).toMatchObject({
|
||||
version: 1,
|
||||
entries: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("records recalls and ranks candidates with weighted scores", async () => {
|
||||
await withTempWorkspace(async (workspaceDir) => {
|
||||
await recordShortTermRecalls({
|
||||
@@ -1015,6 +1047,14 @@ describe("short-term promotion", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("treats diff-prefixed dreaming snippets as contaminated", () => {
|
||||
expect(
|
||||
__testing.isContaminatedDreamingSnippet(
|
||||
"@@ -1,1 - Candidate: Default to action. confidence: 0.76 evidence: memory/.dreams/session-corpus/2026-04-08.txt:1-1 recalls: 3 status: staged",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("skips direct candidates that exceed maxAgeDays during apply", async () => {
|
||||
await withTempWorkspace(async (workspaceDir) => {
|
||||
const applied = await applyShortTermPromotions({
|
||||
|
||||
@@ -33,12 +33,16 @@ const SHORT_TERM_LOCK_WAIT_TIMEOUT_MS = 10_000;
|
||||
const SHORT_TERM_LOCK_STALE_MS = 60_000;
|
||||
const SHORT_TERM_LOCK_RETRY_DELAY_MS = 40;
|
||||
const DREAMING_NARRATIVE_PROMPT_PREFIX = "Write a dream diary entry from these memory fragments";
|
||||
const DREAMING_PROMOTION_META_PREFIX = "openclaw-memory-promotion:";
|
||||
// Repeated dreaming revisits should be able to clear the default promotion gate
|
||||
// without requiring separate organic recall traffic for the same snippet.
|
||||
const PHASE_SIGNAL_LIGHT_BOOST_MAX = 0.06;
|
||||
const PHASE_SIGNAL_REM_BOOST_MAX = 0.09;
|
||||
const PHASE_SIGNAL_HALF_LIFE_DAYS = 14;
|
||||
const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-";
|
||||
const DREAMING_CANDIDATE_LEAD_RE =
|
||||
/^(?:[-*+>]\s+|@@\s*-\d+(?:,\d+)?\s+[-*+]\s+|[([]\s*)*Candidate:/i;
|
||||
const DREAMING_REFLECTION_LEAD_RE =
|
||||
/^(?:[-*+>]\s+|@@\s*-\d+(?:,\d+)?\s+[-*+]\s+|[([]\s*)*Reflections?:/i;
|
||||
const inProcessShortTermLocks = new Map<string, Promise<void>>();
|
||||
const ensuredShortTermDirs = new Map<string, Promise<void>>();
|
||||
|
||||
@@ -244,15 +248,14 @@ function isContaminatedDreamingSnippet(raw: string): boolean {
|
||||
}
|
||||
if (
|
||||
snippet.includes(DREAMING_NARRATIVE_PROMPT_PREFIX) ||
|
||||
snippet.includes(DREAMING_PROMOTION_META_PREFIX) ||
|
||||
snippet.includes("dreaming-narrative-")
|
||||
snippet.includes(PROMOTION_MARKER_PREFIX) ||
|
||||
snippet.includes(DREAMING_NARRATIVE_RUN_PREFIX)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasCandidateLead = /^Candidate:/i.test(snippet) || /[([]\s*Candidate:/i.test(snippet);
|
||||
const hasReflectionLead =
|
||||
/^Reflections?:/i.test(snippet) || /[([]\s*Reflections?:/i.test(snippet);
|
||||
const hasCandidateLead = DREAMING_CANDIDATE_LEAD_RE.test(snippet);
|
||||
const hasReflectionLead = DREAMING_REFLECTION_LEAD_RE.test(snippet);
|
||||
const hasConfidence = /\bconfidence:\s*\d/i.test(snippet);
|
||||
const hasEvidence = /\bevidence:\s*(?:memory\/\.dreams\/session-corpus\/|memory\/)/i.test(
|
||||
snippet,
|
||||
|
||||
@@ -203,4 +203,29 @@ describe("buildSessionEntry", () => {
|
||||
expect(entry?.content).toContain("Assistant: A drifting archive breathed in moonlight.");
|
||||
expect(entry?.lineMap).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it("does not flag transcripts when dreaming markers only appear mid-string", async () => {
|
||||
const jsonlLines = [
|
||||
JSON.stringify({
|
||||
type: "custom",
|
||||
customType: "note",
|
||||
data: {
|
||||
runId: "user-context-dreaming-narrative-light-1775894400455",
|
||||
},
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "message",
|
||||
message: { role: "user", content: "Keep the archive index updated." },
|
||||
}),
|
||||
];
|
||||
const filePath = path.join(tmpDir, "substring-marker-session.jsonl");
|
||||
await fs.writeFile(filePath, jsonlLines.join("\n"));
|
||||
|
||||
const entry = await buildSessionEntry(filePath);
|
||||
|
||||
expect(entry).not.toBeNull();
|
||||
expect(entry?.generatedByDreamingNarrative).toBeUndefined();
|
||||
expect(entry?.content).toContain("User: Keep the archive index updated.");
|
||||
expect(entry?.lineMap).toEqual([2]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ function isDreamingNarrativeBootstrapRecord(record: unknown): boolean {
|
||||
}
|
||||
|
||||
function hasDreamingNarrativeRunId(value: unknown): boolean {
|
||||
return typeof value === "string" && value.includes(DREAMING_NARRATIVE_RUN_PREFIX);
|
||||
return typeof value === "string" && value.startsWith(DREAMING_NARRATIVE_RUN_PREFIX);
|
||||
}
|
||||
|
||||
function isDreamingNarrativeGeneratedRecord(record: unknown): boolean {
|
||||
|
||||
Reference in New Issue
Block a user