mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 08:11:39 +00:00
* refactor(config): consolidate media model lists * refactor(config): unify memory configuration * refactor(config): consolidate TTS ownership * refactor(config): move typing policy to agents * refactor(config): retire product-level config surfaces * refactor(config): share scoped tool policy type * chore(config): refresh generated baselines * fix(config): honor agent typing overrides * fix(config): migrate sibling config consumers * refactor(infra): keep base64url decoder private * fix(config): strip invalid legacy TTS values * chore(config): refresh rebased baseline hash * fix(doctor): route legacy messages.tts.realtime voice to talk during tts move * refactor(config): polish final layout names * refactor(config): freeze retired tuning defaults * feat(config): add fast mode default symmetry * refactor(config): key agent entries by id * docs(config): update final layout reference * test(config): cover final layout migrations * chore(config): refresh final layout baselines * fix(config): align final layout runtime readers * fix(config): align remaining readers * fix(config): stabilize final layout migrations * fix(config): finalize config projection proof * fix(config): address final layout review * docs(release): preserve historical config names * fix(config): complete keyed agent migration * fix(config): close final migration gaps * fix(config): finish full-branch review * fix(config): complete runtime secret detection * fix(config): close final review findings * fix(config): finish canonical docs and heartbeat migration * fix(config): integrate latest main after rebase * refactor(env): isolate test-only controls * refactor(env): isolate build and development controls * refactor(env): collapse process identity indirection * refactor(env): remove duplicate config and temp aliases * docs(env): define the operator-facing allowlist * ci(env): ratchet production variable count * fix(env): remove stale provider helper import * fix(env): make ratchet sorting explicit * test(env): keep test seam in dead-code audit * test(env): cover ratchet growth and boundary; document surface budgets * docs(config): document tier-eval consolidations * docs(config): clarify speech preference ownership * test(memory): align retired tuning fixtures * refactor(memory): freeze engine heuristics * refactor(config): apply tier-eval tranche * refactor(tts): move persona shaping to providers * refactor(compaction): move prompt policy to providers * test(config): align hookified prompt fixtures * chore(deadcode): classify test-only exports * chore(github): remove unused spawn helper * chore(deadcode): classify queue diagnostics * chore(deadcode): remove unused lane snapshot export * chore(plugin-sdk): ratchet consolidated surface * fix(config): integrate latest main after rebase
137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
// Memory Core plugin module implements flush plan behavior.
|
|
import {
|
|
DEFAULT_AGENT_COMPACTION_RESERVE_TOKENS_FLOOR,
|
|
parseNonNegativeByteSize,
|
|
resolveCronStyleNow,
|
|
SILENT_REPLY_TOKEN,
|
|
type MemoryFlushPlan,
|
|
type OpenClawConfig,
|
|
} from "openclaw/plugin-sdk/memory-core-host-runtime-core";
|
|
import { resolveMemoryCoreNowMs } from "./time.js";
|
|
|
|
const DEFAULT_MEMORY_FLUSH_SOFT_TOKENS = 4000;
|
|
const DEFAULT_MEMORY_FLUSH_FORCE_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
|
|
|
|
const MEMORY_FLUSH_TARGET_HINT =
|
|
"Store durable memories only in memory/YYYY-MM-DD.md (create memory/ if needed).";
|
|
const MEMORY_FLUSH_APPEND_ONLY_HINT =
|
|
"If memory/YYYY-MM-DD.md already exists, APPEND new content only and do not overwrite existing entries.";
|
|
const MEMORY_FLUSH_READ_ONLY_HINT =
|
|
"Treat workspace bootstrap/reference files such as MEMORY.md, DREAMS.md, SOUL.md, TOOLS.md, and AGENTS.md as read-only during this flush; never overwrite, replace, or edit them.";
|
|
const MEMORY_FLUSH_REQUIRED_HINTS = [
|
|
MEMORY_FLUSH_TARGET_HINT,
|
|
MEMORY_FLUSH_APPEND_ONLY_HINT,
|
|
MEMORY_FLUSH_READ_ONLY_HINT,
|
|
];
|
|
|
|
const DEFAULT_MEMORY_FLUSH_PROMPT = [
|
|
"Pre-compaction memory flush.",
|
|
MEMORY_FLUSH_TARGET_HINT,
|
|
MEMORY_FLUSH_READ_ONLY_HINT,
|
|
MEMORY_FLUSH_APPEND_ONLY_HINT,
|
|
"Do NOT create timestamped variant files (e.g., YYYY-MM-DD-HHMM.md); always use the canonical YYYY-MM-DD.md filename.",
|
|
`If nothing to store, reply with ${SILENT_REPLY_TOKEN}.`,
|
|
].join(" ");
|
|
|
|
const DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT = [
|
|
"Pre-compaction memory flush turn.",
|
|
"The session is near auto-compaction; capture durable memories to disk.",
|
|
MEMORY_FLUSH_TARGET_HINT,
|
|
MEMORY_FLUSH_READ_ONLY_HINT,
|
|
MEMORY_FLUSH_APPEND_ONLY_HINT,
|
|
`You may reply, but usually ${SILENT_REPLY_TOKEN} is correct.`,
|
|
].join(" ");
|
|
|
|
function formatDateStampInTimezone(nowMs: number, timezone: string): string {
|
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
timeZone: timezone,
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
}).formatToParts(new Date(nowMs));
|
|
const year = parts.find((part) => part.type === "year")?.value;
|
|
const month = parts.find((part) => part.type === "month")?.value;
|
|
const day = parts.find((part) => part.type === "day")?.value;
|
|
if (year && month && day) {
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
return new Date(resolveMemoryCoreNowMs(nowMs)).toISOString().slice(0, 10);
|
|
}
|
|
|
|
function normalizeNonNegativeInt(value: unknown): number | null {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
return null;
|
|
}
|
|
const int = Math.floor(value);
|
|
return int >= 0 ? int : null;
|
|
}
|
|
|
|
function ensureNoReplyHint(text: string): string {
|
|
if (text.includes(SILENT_REPLY_TOKEN)) {
|
|
return text;
|
|
}
|
|
return `${text}\n\nIf no user-visible reply is needed, start with ${SILENT_REPLY_TOKEN}.`;
|
|
}
|
|
|
|
function ensureMemoryFlushSafetyHints(text: string): string {
|
|
let next = text.trim();
|
|
for (const hint of MEMORY_FLUSH_REQUIRED_HINTS) {
|
|
if (!next.includes(hint)) {
|
|
next = next ? `${next}\n\n${hint}` : hint;
|
|
}
|
|
}
|
|
return next;
|
|
}
|
|
|
|
function appendCurrentTimeLine(text: string, timeLine: string): string {
|
|
const trimmed = text.trimEnd();
|
|
if (!trimmed) {
|
|
return timeLine;
|
|
}
|
|
if (trimmed.includes("Current time:")) {
|
|
return trimmed;
|
|
}
|
|
return `${trimmed}\n${timeLine}`;
|
|
}
|
|
|
|
export function buildMemoryFlushPlan(
|
|
params: {
|
|
cfg?: OpenClawConfig;
|
|
nowMs?: number;
|
|
} = {},
|
|
): MemoryFlushPlan | null {
|
|
const resolved = params;
|
|
const nowMs = resolveMemoryCoreNowMs(resolved.nowMs);
|
|
const cfg = resolved.cfg;
|
|
const defaults = cfg?.agents?.defaults?.compaction?.memoryFlush;
|
|
if (defaults?.enabled === false) {
|
|
return null;
|
|
}
|
|
|
|
const softThresholdTokens =
|
|
normalizeNonNegativeInt(defaults?.softThresholdTokens) ?? DEFAULT_MEMORY_FLUSH_SOFT_TOKENS;
|
|
const forceFlushTranscriptBytes =
|
|
parseNonNegativeByteSize(defaults?.forceFlushTranscriptBytes) ??
|
|
DEFAULT_MEMORY_FLUSH_FORCE_TRANSCRIPT_BYTES;
|
|
const reserveTokensFloor = DEFAULT_AGENT_COMPACTION_RESERVE_TOKENS_FLOOR;
|
|
|
|
const { timeLine, userTimezone } = resolveCronStyleNow(cfg ?? {}, nowMs);
|
|
const dateStamp = formatDateStampInTimezone(nowMs, userTimezone);
|
|
const relativePath = `memory/${dateStamp}.md`;
|
|
|
|
const promptBase = ensureNoReplyHint(ensureMemoryFlushSafetyHints(DEFAULT_MEMORY_FLUSH_PROMPT));
|
|
const systemPrompt = ensureNoReplyHint(
|
|
ensureMemoryFlushSafetyHints(DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT),
|
|
);
|
|
|
|
return {
|
|
softThresholdTokens,
|
|
forceFlushTranscriptBytes,
|
|
reserveTokensFloor,
|
|
model: defaults?.model?.trim() || undefined,
|
|
prompt: appendCurrentTimeLine(promptBase.replaceAll("YYYY-MM-DD", dateStamp), timeLine),
|
|
systemPrompt: systemPrompt.replaceAll("YYYY-MM-DD", dateStamp),
|
|
relativePath,
|
|
};
|
|
}
|