Skip to content

Commit 847739d

Browse files
authored
Fix dreaming replay, repair polluted artifacts, and gate wiki tabs (#65138)
* fix(active-memory): preserve parent channel context for recall runs * fix(active-memory): keep recall runs on the resolved channel * fix(active-memory): prefer resolved recall channel over wrapper hints * fix(active-memory): trust explicit recall channel hints * fix(active-memory): rank recall channel fallbacks by trust * Fix dreaming replay and recovery flows * fix: prevent dreaming event loss and diary write races * chore: add changelog entry for memory fixes * fix: harden dreaming repair and diary writes * fix: harden dreaming artifact archive naming
1 parent 5543925 commit 847739d

45 files changed

Lines changed: 2016 additions & 148 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
88

99
### Fixes
1010

11+
- Memory/active-memory+dreaming: keep active-memory recall runs on the strongest resolved channel, consume managed dreaming heartbeat events exactly once, stop dreaming from re-ingesting its own narrative transcripts, and add explicit repair/dedupe recovery flows in CLI, doctor, and the Dreams UI.
1112
- Matrix/mentions: keep room mention gating strict while accepting visible `@displayName` Matrix URI labels, so `requireMention` works for non-OpenClaw Matrix clients again. (#64796) Thanks @hclsys.
1213
- Doctor: warn when on-disk agent directories still exist under `~/.openclaw/agents/<id>/agent` but the matching `agents.list[]` entries are missing from config. (#65113) Thanks @neeravmakwana.
1314
- Telegram: route approval button callback queries onto a separate sequentializer lane so plugin approval clicks can resolve immediately instead of deadlocking behind the blocked agent turn. (#64979) Thanks @nk3750.

extensions/active-memory/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,10 @@ function buildPluginDebugLine(params: {
10331033
if (fallback) {
10341034
debugParts.push(`fallback=${fallback}`);
10351035
}
1036-
if (typeof params.searchDebug?.searchMs === "number" && Number.isFinite(params.searchDebug.searchMs)) {
1036+
if (
1037+
typeof params.searchDebug?.searchMs === "number" &&
1038+
Number.isFinite(params.searchDebug.searchMs)
1039+
) {
10371040
debugParts.push(`searchMs=${Math.max(0, Math.round(params.searchDebug.searchMs))}`);
10381041
}
10391042
if (typeof params.searchDebug?.hits === "number" && Number.isFinite(params.searchDebug.hits)) {
@@ -1220,7 +1223,9 @@ function normalizeSearchDebug(value: unknown): ActiveMemorySearchDebug | undefin
12201223
: undefined;
12211224
}
12221225

1223-
function readActiveMemorySearchDebugFromRunResult(result: unknown): ActiveMemorySearchDebug | undefined {
1226+
function readActiveMemorySearchDebugFromRunResult(
1227+
result: unknown,
1228+
): ActiveMemorySearchDebug | undefined {
12241229
const record = asRecord(result);
12251230
const meta = asRecord(record?.meta);
12261231
return (

extensions/discord/src/monitor/native-command.options.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ function requireAutocomplete(option: CommandOption, errorMessage: string) {
113113
if (typeof autocomplete !== "function") {
114114
throw new Error(errorMessage);
115115
}
116-
return autocomplete;
116+
return autocomplete as (interaction: unknown) => Promise<unknown>;
117117
}
118118

119119
async function runAutocomplete(
120-
autocomplete: (interaction: never) => Promise<unknown>,
120+
autocomplete: (interaction: unknown) => Promise<unknown>,
121121
params: {
122122
userId: string;
123123
username?: string;

extensions/memory-core/api.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ export type {
44
MemoryProviderStatus,
55
MemorySyncProgressUpdate,
66
} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
7-
export { removeBackfillDiaryEntries, writeBackfillDiaryEntries } from "./src/dreaming-narrative.js";
7+
export {
8+
dedupeDreamDiaryEntries,
9+
removeBackfillDiaryEntries,
10+
writeBackfillDiaryEntries,
11+
} from "./src/dreaming-narrative.js";
812
export { previewGroundedRemMarkdown } from "./src/rem-evidence.js";

extensions/memory-core/runtime-api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ export {
1515
} from "openclaw/plugin-sdk/memory-core-host-status";
1616
export { checkQmdBinaryAvailability } from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
1717
export { hasConfiguredMemorySecretInput } from "openclaw/plugin-sdk/memory-core-host-secret";
18+
export { auditDreamingArtifacts, repairDreamingArtifacts } from "./src/dreaming-repair.js";
1819
export {
1920
auditShortTermPromotionArtifacts,
2021
removeGroundedShortTermCandidates,
2122
repairShortTermPromotionArtifacts,
2223
} from "./src/short-term-promotion.js";
2324
export type { BuiltinMemoryEmbeddingProviderDoctorMetadata } from "./src/memory/provider-adapters.js";
25+
export type {
26+
DreamingArtifactsAuditSummary,
27+
RepairDreamingArtifactsResult,
28+
} from "./src/dreaming-repair.js";
2429
export type {
2530
RepairShortTermPromotionArtifactsResult,
2631
ShortTermAuditSummary,

extensions/memory-core/src/cli.runtime.ts

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ import type {
3737
} from "./cli.types.js";
3838
import { removeBackfillDiaryEntries, writeBackfillDiaryEntries } from "./dreaming-narrative.js";
3939
import { previewRemDreaming, seedHistoricalDailyMemorySignals } from "./dreaming-phases.js";
40+
import {
41+
auditDreamingArtifacts,
42+
repairDreamingArtifacts,
43+
type DreamingArtifactsAuditSummary,
44+
type RepairDreamingArtifactsResult,
45+
} from "./dreaming-repair.js";
4046
import { asRecord } from "./dreaming-shared.js";
4147
import { resolveShortTermPromotionDreamingConfig } from "./dreaming.js";
4248
import { previewGroundedRemMarkdown } from "./rem-evidence.js";
@@ -249,6 +255,35 @@ function formatRepairSummary(repair: RepairShortTermPromotionArtifactsResult): s
249255
return actions.length > 0 ? actions.join(" · ") : "no changes";
250256
}
251257

258+
function formatDreamingAuditSummary(audit: DreamingArtifactsAuditSummary): string {
259+
const bits = [
260+
audit.dreamsPath ? "diary present" : "diary absent",
261+
`${audit.sessionCorpusFileCount} corpus files`,
262+
audit.sessionIngestionExists ? "ingestion state present" : "ingestion state absent",
263+
audit.suspiciousSessionCorpusLineCount > 0
264+
? `${audit.suspiciousSessionCorpusLineCount} suspicious lines`
265+
: null,
266+
].filter(Boolean);
267+
return bits.join(" · ");
268+
}
269+
270+
function formatDreamingRepairSummary(repair: RepairDreamingArtifactsResult): string {
271+
const actions: string[] = [];
272+
if (repair.archivedSessionCorpus) {
273+
actions.push("archived session corpus");
274+
}
275+
if (repair.archivedSessionIngestion) {
276+
actions.push("archived ingestion state");
277+
}
278+
if (repair.archivedDreamsDiary) {
279+
actions.push("archived diary");
280+
}
281+
if (repair.warnings.length > 0) {
282+
actions.push(`${repair.warnings.length} warning${repair.warnings.length === 1 ? "" : "s"}`);
283+
}
284+
return actions.length > 0 ? actions.join(" · ") : "no changes";
285+
}
286+
252287
function formatSourceLabel(source: string, workspaceDir: string, agentId: string): string {
253288
if (source === "memory") {
254289
return shortenHomeInString(
@@ -648,6 +683,8 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
648683
scan?: MemorySourceScan;
649684
audit?: ShortTermAuditSummary;
650685
repair?: RepairShortTermPromotionArtifactsResult;
686+
dreamingAudit?: DreamingArtifactsAuditSummary;
687+
dreamingRepair?: RepairDreamingArtifactsResult;
651688
}> = [];
652689

653690
for (const agentId of agentIds) {
@@ -723,7 +760,14 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
723760
: undefined;
724761
let audit: ShortTermAuditSummary | undefined;
725762
let repair: RepairShortTermPromotionArtifactsResult | undefined;
763+
let dreamingAudit: DreamingArtifactsAuditSummary | undefined;
764+
let dreamingRepair: RepairDreamingArtifactsResult | undefined;
726765
if (workspaceDir) {
766+
dreamingAudit = await auditDreamingArtifacts({ workspaceDir });
767+
if (opts.fix && dreamingAudit.issues.some((issue) => issue.fixable)) {
768+
dreamingRepair = await repairDreamingArtifacts({ workspaceDir });
769+
dreamingAudit = await auditDreamingArtifacts({ workspaceDir });
770+
}
727771
if (opts.fix) {
728772
repair = await repairShortTermPromotionArtifacts({ workspaceDir });
729773
}
@@ -742,7 +786,17 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
742786
: undefined,
743787
});
744788
}
745-
allResults.push({ agentId, status, embeddingProbe, indexError, scan, audit, repair });
789+
allResults.push({
790+
agentId,
791+
status,
792+
embeddingProbe,
793+
indexError,
794+
scan,
795+
audit,
796+
repair,
797+
dreamingAudit,
798+
dreamingRepair,
799+
});
746800
},
747801
});
748802
}
@@ -762,7 +816,17 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
762816
const label = (text: string) => muted(`${text}:`);
763817

764818
for (const result of allResults) {
765-
const { agentId, status, embeddingProbe, indexError, scan, audit, repair } = result;
819+
const {
820+
agentId,
821+
status,
822+
embeddingProbe,
823+
indexError,
824+
scan,
825+
audit,
826+
repair,
827+
dreamingAudit,
828+
dreamingRepair,
829+
} = result;
766830
const filesIndexed = status.files ?? 0;
767831
const chunksIndexed = status.chunks ?? 0;
768832
const totalFiles = scan?.totalFiles ?? null;
@@ -898,9 +962,29 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
898962
lines.push(`${label("QMD audit")} ${info(qmdBits.join(" · "))}`);
899963
}
900964
}
965+
if (dreamingAudit) {
966+
lines.push(
967+
`${label("Dreaming artifacts")} ${info(formatDreamingAuditSummary(dreamingAudit))}`,
968+
);
969+
lines.push(
970+
`${label("Dream corpus")} ${info(shortenHomePath(dreamingAudit.sessionCorpusDir))}`,
971+
);
972+
lines.push(
973+
`${label("Dream ingestion")} ${info(shortenHomePath(dreamingAudit.sessionIngestionPath))}`,
974+
);
975+
if (dreamingAudit.dreamsPath) {
976+
lines.push(`${label("Dream diary")} ${info(shortenHomePath(dreamingAudit.dreamsPath))}`);
977+
}
978+
}
901979
if (repair) {
902980
lines.push(`${label("Repair")} ${info(formatRepairSummary(repair))}`);
903981
}
982+
if (dreamingRepair) {
983+
lines.push(`${label("Dream repair")} ${info(formatDreamingRepairSummary(dreamingRepair))}`);
984+
if (dreamingRepair.archiveDir) {
985+
lines.push(`${label("Dream archive")} ${info(shortenHomePath(dreamingRepair.archiveDir))}`);
986+
}
987+
}
904988
if (status.fallback?.reason) {
905989
lines.push(muted(status.fallback.reason));
906990
}
@@ -924,6 +1008,17 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
9241008
lines.push(` ${muted(`Fix: openclaw memory status --fix --agent ${agentId}`)}`);
9251009
}
9261010
}
1011+
if (dreamingAudit?.issues.length) {
1012+
if (!scan?.issues.length && !audit?.issues.length) {
1013+
lines.push(label("Issues"));
1014+
}
1015+
for (const issue of dreamingAudit.issues) {
1016+
lines.push(` ${issue.severity === "error" ? warn(issue.message) : muted(issue.message)}`);
1017+
}
1018+
if (!opts.fix) {
1019+
lines.push(` ${muted(`Fix: openclaw memory status --fix --agent ${agentId}`)}`);
1020+
}
1021+
}
9271022
defaultRuntime.log(lines.join("\n"));
9281023
defaultRuntime.log("");
9291024
}

extensions/memory-core/src/cli.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,50 @@ describe("memory cli", () => {
474474
});
475475
});
476476

477+
it("repairs contaminated dreaming artifacts during status --fix", async () => {
478+
await withTempWorkspace(async (workspaceDir) => {
479+
const sessionCorpusDir = path.join(workspaceDir, "memory", ".dreams", "session-corpus");
480+
await fs.mkdir(sessionCorpusDir, { recursive: true });
481+
await fs.writeFile(
482+
path.join(sessionCorpusDir, "2026-04-11.txt"),
483+
[
484+
"[main/dreaming-main.jsonl#L3] ordinary session line",
485+
"[main/dreaming-narrative-light.jsonl#L1] Write a dream diary entry from these memory fragments:",
486+
].join("\n"),
487+
"utf-8",
488+
);
489+
await fs.writeFile(
490+
path.join(workspaceDir, "memory", ".dreams", "session-ingestion.json"),
491+
JSON.stringify({ version: 3, files: {}, seenMessages: {} }, null, 2),
492+
"utf-8",
493+
);
494+
await fs.writeFile(path.join(workspaceDir, "DREAMS.md"), "# Dream Diary\n", "utf-8");
495+
496+
const close = vi.fn(async () => {});
497+
mockManager({
498+
probeVectorAvailability: vi.fn(async () => true),
499+
status: () => makeMemoryStatus({ workspaceDir }),
500+
close,
501+
});
502+
503+
const log = spyRuntimeLogs(defaultRuntime);
504+
await runMemoryCli(["status", "--fix"]);
505+
506+
expect(log).toHaveBeenCalledWith(
507+
expect.stringContaining("Dream repair: archived session corpus"),
508+
);
509+
expect(log).toHaveBeenCalledWith(expect.stringContaining("Dream archive:"));
510+
await expect(fs.access(sessionCorpusDir)).rejects.toMatchObject({ code: "ENOENT" });
511+
await expect(
512+
fs.access(path.join(workspaceDir, "memory", ".dreams", "session-ingestion.json")),
513+
).rejects.toMatchObject({ code: "ENOENT" });
514+
await expect(fs.readFile(path.join(workspaceDir, "DREAMS.md"), "utf-8")).resolves.toContain(
515+
"# Dream Diary",
516+
);
517+
expect(close).toHaveBeenCalled();
518+
});
519+
});
520+
477521
it("enables verbose logging with --verbose", async () => {
478522
const close = vi.fn(async () => {});
479523
mockManager({

0 commit comments

Comments
 (0)