fix(memory): accept embedded dreaming heartbeat tokens

This commit is contained in:
Peter Steinberger
2026-04-08 19:54:32 +01:00
parent 5478462cbf
commit 79f440c903
4 changed files with 58 additions and 36 deletions

View File

@@ -34,11 +34,11 @@ import type {
MemoryRemHarnessOptions,
MemorySearchCommandOptions,
} from "./cli.types.js";
import { previewRemDreaming, seedHistoricalDailyMemorySignals } from "./dreaming-phases.js";
import { removeBackfillDiaryEntries, writeBackfillDiaryEntries } from "./dreaming-narrative.js";
import { previewGroundedRemMarkdown } from "./rem-evidence.js";
import { previewRemDreaming, seedHistoricalDailyMemorySignals } from "./dreaming-phases.js";
import { asRecord } from "./dreaming-shared.js";
import { resolveShortTermPromotionDreamingConfig } from "./dreaming.js";
import { previewGroundedRemMarkdown } from "./rem-evidence.js";
import {
applyShortTermPromotions,
auditShortTermPromotionArtifacts,
@@ -154,7 +154,9 @@ async function createHistoricalRemHarnessWorkspace(params: {
for (const filePath of sourceFiles) {
await fs.copyFile(filePath, path.join(memoryDir, path.basename(filePath)));
}
const workspaceSourceFiles = sourceFiles.map((entry) => path.join(memoryDir, path.basename(entry)));
const workspaceSourceFiles = sourceFiles.map((entry) =>
path.join(memoryDir, path.basename(entry)),
);
const seeded = await seedHistoricalDailyMemorySignals({
workspaceDir,
filePaths: workspaceSourceFiles,
@@ -1422,14 +1424,13 @@ export async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) {
workspaceDir,
sourcePath: opts.path ? path.resolve(opts.path) : null,
sourceFiles,
historicalImport:
opts.path
? {
importedFileCount,
importedSignalCount,
skippedPaths,
}
: null,
historicalImport: opts.path
? {
importedFileCount,
importedSignalCount,
skippedPaths,
}
: null,
remConfig,
deepConfig: {
minScore: deep.minScore,
@@ -1497,7 +1498,7 @@ export async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) {
"",
colorize(rich, theme.heading, "Grounded REM"),
...groundedPreview.files.flatMap((file) => [
colorize(rich, theme.label, file.path),
colorize(rich, theme.muted, file.path),
file.renderedMarkdown,
"",
]),
@@ -1570,7 +1571,9 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) {
}
if (!opts.path) {
defaultRuntime.error("Memory rem-backfill requires --path <file-or-dir> unless using --rollback.");
defaultRuntime.error(
"Memory rem-backfill requires --path <file-or-dir> unless using --rollback.",
);
process.exitCode = 1;
return;
}
@@ -1636,7 +1639,11 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) {
`${colorize(rich, theme.heading, "REM Backfill")} ${colorize(rich, theme.muted, `(${agentId})`)}`,
colorize(rich, theme.muted, `workspace=${shortenHomePath(workspaceDir)}`),
colorize(rich, theme.muted, `sourcePath=${shortenHomePath(path.resolve(opts.path))}`),
colorize(rich, theme.muted, `historicalFiles=${sourceFiles.length} writtenEntries=${written.written} replacedEntries=${written.replaced}`),
colorize(
rich,
theme.muted,
`historicalFiles=${sourceFiles.length} writtenEntries=${written.written} replacedEntries=${written.replaced}`,
),
colorize(rich, theme.muted, `dreamsPath=${shortenHomePath(written.dreamsPath)}`),
].join("\n"),
);

View File

@@ -1110,6 +1110,7 @@ describe("memory cli", () => {
grounded?: {
files?: Array<{
renderedMarkdown?: string;
reflections?: Array<{ text: string }>;
}>;
} | null;
}>(writeJson);
@@ -1162,6 +1163,7 @@ describe("memory cli", () => {
grounded?: {
files?: Array<{
renderedMarkdown?: string;
reflections?: Array<{ text: string }>;
}>;
} | null;
}>(writeJson);
@@ -1206,6 +1208,7 @@ describe("memory cli", () => {
grounded?: {
files?: Array<{
renderedMarkdown?: string;
reflections?: Array<{ text: string }>;
}>;
} | null;
}>(writeJson);

View File

@@ -29,6 +29,28 @@ import {
} from "./short-term-promotion.js";
type Logger = Pick<OpenClawPluginApi["logger"], "info" | "warn" | "error">;
type DreamingPhaseStorageConfig = {
timezone?: string;
storage: { mode: "inline" | "separate" | "both"; separateReports: boolean };
};
type RunPhaseIfTriggeredParams = {
cleanedBody: string;
trigger?: string;
workspaceDir?: string;
cfg?: OpenClawConfig;
logger: Logger;
subagent?: Parameters<typeof generateAndAppendDreamNarrative>[0]["subagent"];
eventText: string;
} & (
| {
phase: "light";
config: MemoryLightDreamingConfig & DreamingPhaseStorageConfig;
}
| {
phase: "rem";
config: MemoryRemDreamingConfig & DreamingPhaseStorageConfig;
}
);
const LIGHT_SLEEP_EVENT_TEXT = "__openclaw_memory_core_light_sleep__";
const REM_SLEEP_EVENT_TEXT = "__openclaw_memory_core_rem_sleep__";
const DAILY_MEMORY_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})\.md$/;
@@ -1102,7 +1124,9 @@ export async function seedHistoricalDailyMemorySignals(params: {
importedSignalCount: number;
skippedPaths: string[];
}> {
const normalizedPaths = [...new Set(params.filePaths.map((entry) => entry.trim()).filter(Boolean))];
const normalizedPaths = [
...new Set(params.filePaths.map((entry) => entry.trim()).filter(Boolean)),
];
if (normalizedPaths.length === 0) {
return {
importedFileCount: 0,
@@ -1133,7 +1157,9 @@ export async function seedHistoricalDailyMemorySignals(params: {
return a.filePath.localeCompare(b.filePath);
});
const valid = resolved.filter((entry): entry is { filePath: string; day: string } => Boolean(entry.day));
const valid = resolved.filter((entry): entry is { filePath: string; day: string } =>
Boolean(entry.day),
);
const skippedPaths = resolved.filter((entry) => !entry.day).map((entry) => entry.filePath);
const totalCap = Math.max(20, params.limit * 4);
const perFileCap = Math.max(6, Math.ceil(totalCap / Math.max(1, valid.length)));
@@ -1605,26 +1631,11 @@ export async function runDreamingSweepPhases(params: {
}
}
async function runPhaseIfTriggered(params: {
cleanedBody: string;
trigger?: string;
workspaceDir?: string;
cfg?: OpenClawConfig;
logger: Logger;
subagent?: Parameters<typeof generateAndAppendDreamNarrative>[0]["subagent"];
phase: "light" | "rem";
eventText: string;
config:
| (MemoryLightDreamingConfig & {
timezone?: string;
storage: { mode: "inline" | "separate" | "both"; separateReports: boolean };
})
| (MemoryRemDreamingConfig & {
timezone?: string;
storage: { mode: "inline" | "separate" | "both"; separateReports: boolean };
});
}): Promise<{ handled: true; reason: string } | undefined> {
if (params.trigger !== "heartbeat" || params.cleanedBody.trim() !== params.eventText) {
async function runPhaseIfTriggered(
params: RunPhaseIfTriggeredParams,
): Promise<{ handled: true; reason: string } | undefined> {
const hasEventToken = params.cleanedBody.trim().split(/\s+/).includes(params.eventText);
if (params.trigger !== "heartbeat" || !hasEventToken) {
return undefined;
}
if (!params.config.enabled) {

View File

@@ -70,6 +70,7 @@ type SettingsHost = {
dreamingStatus: null;
dreamingModeSaving: boolean;
dreamDiaryLoading: boolean;
dreamDiaryActionLoading: boolean;
dreamDiaryError: string | null;
dreamDiaryPath: string | null;
dreamDiaryContent: string | null;