mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-14 18:51:04 +00:00
201 lines
6.5 KiB
TypeScript
201 lines
6.5 KiB
TypeScript
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memory-core";
|
|
import {
|
|
resolveMemoryLightDreamingConfig,
|
|
resolveMemoryRemDreamingConfig,
|
|
resolveMemoryDreamingConfig,
|
|
} from "openclaw/plugin-sdk/memory-core-host-status";
|
|
import { resolveShortTermPromotionDreamingConfig } from "./dreaming.js";
|
|
|
|
type DreamingPhaseName = "light" | "deep" | "rem";
|
|
|
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function normalizeDreamingPhase(value: unknown): DreamingPhaseName | null {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
const normalized = value.trim().toLowerCase();
|
|
if (normalized === "light" || normalized === "deep" || normalized === "rem") {
|
|
return normalized;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function resolveMemoryCorePluginConfig(cfg: OpenClawConfig): Record<string, unknown> {
|
|
const entry = asRecord(cfg.plugins?.entries?.["memory-core"]);
|
|
return asRecord(entry?.config) ?? {};
|
|
}
|
|
|
|
function updateDreamingEnabledInConfig(cfg: OpenClawConfig, enabled: boolean): OpenClawConfig {
|
|
const entries = { ...(cfg.plugins?.entries ?? {}) };
|
|
const existingEntry = asRecord(entries["memory-core"]) ?? {};
|
|
const existingConfig = asRecord(existingEntry.config) ?? {};
|
|
const existingSleep = asRecord(existingConfig.dreaming) ?? {};
|
|
entries["memory-core"] = {
|
|
...existingEntry,
|
|
config: {
|
|
...existingConfig,
|
|
dreaming: {
|
|
...existingSleep,
|
|
enabled,
|
|
},
|
|
},
|
|
};
|
|
|
|
return {
|
|
...cfg,
|
|
plugins: {
|
|
...cfg.plugins,
|
|
entries,
|
|
},
|
|
};
|
|
}
|
|
|
|
function updateDreamingPhaseEnabledInConfig(
|
|
cfg: OpenClawConfig,
|
|
phase: DreamingPhaseName,
|
|
enabled: boolean,
|
|
): OpenClawConfig {
|
|
const entries = { ...(cfg.plugins?.entries ?? {}) };
|
|
const existingEntry = asRecord(entries["memory-core"]) ?? {};
|
|
const existingConfig = asRecord(existingEntry.config) ?? {};
|
|
const existingSleep = asRecord(existingConfig.dreaming) ?? {};
|
|
const existingPhases = asRecord(existingSleep.phases) ?? {};
|
|
const existingPhase = asRecord(existingPhases[phase]) ?? {};
|
|
entries["memory-core"] = {
|
|
...existingEntry,
|
|
config: {
|
|
...existingConfig,
|
|
dreaming: {
|
|
...existingSleep,
|
|
phases: {
|
|
...existingPhases,
|
|
[phase]: {
|
|
...existingPhase,
|
|
enabled,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
return {
|
|
...cfg,
|
|
plugins: {
|
|
...cfg.plugins,
|
|
entries,
|
|
},
|
|
};
|
|
}
|
|
|
|
function formatEnabled(value: boolean): string {
|
|
return value ? "on" : "off";
|
|
}
|
|
|
|
function formatPhaseGuide(): string {
|
|
return [
|
|
"- light: sorts recent memory traces into the daily note.",
|
|
"- deep: promotes durable memories into MEMORY.md and handles recovery when memory is thin.",
|
|
"- rem: writes reflection and pattern notes into the daily note.",
|
|
].join("\n");
|
|
}
|
|
|
|
function formatStatus(cfg: OpenClawConfig): string {
|
|
const pluginConfig = resolveMemoryCorePluginConfig(cfg);
|
|
const dreaming = resolveMemoryDreamingConfig({
|
|
pluginConfig,
|
|
cfg,
|
|
});
|
|
const deep = resolveShortTermPromotionDreamingConfig({ pluginConfig, cfg });
|
|
const light = resolveMemoryLightDreamingConfig({ pluginConfig, cfg });
|
|
const rem = resolveMemoryRemDreamingConfig({ pluginConfig, cfg });
|
|
const timezone = dreaming.timezone ? ` (${dreaming.timezone})` : "";
|
|
|
|
return [
|
|
"Dreaming status:",
|
|
`- enabled: ${formatEnabled(dreaming.enabled)}${timezone}`,
|
|
`- storage: ${dreaming.storage.mode}${dreaming.storage.separateReports ? " + reports" : ""}`,
|
|
`- verboseLogging: ${formatEnabled(dreaming.verboseLogging)}`,
|
|
`- light: ${formatEnabled(light.enabled)} · cadence=${light.enabled ? light.cron : "disabled"} · lookbackDays=${light.lookbackDays} · limit=${light.limit}`,
|
|
`- deep: ${formatEnabled(deep.enabled)} · cadence=${deep.enabled ? deep.cron : "disabled"} · limit=${deep.limit} · minScore=${deep.minScore} · minRecallCount=${deep.minRecallCount} · minUniqueQueries=${deep.minUniqueQueries} · recencyHalfLifeDays=${deep.recencyHalfLifeDays} · maxAgeDays=${deep.maxAgeDays ?? "none"}`,
|
|
`- rem: ${formatEnabled(rem.enabled)} · cadence=${rem.enabled ? rem.cron : "disabled"} · lookbackDays=${rem.lookbackDays} · limit=${rem.limit} · minPatternStrength=${rem.minPatternStrength}`,
|
|
].join("\n");
|
|
}
|
|
|
|
function formatUsage(includeStatus: string): string {
|
|
return [
|
|
"Usage: /dreaming status",
|
|
"Usage: /dreaming on|off",
|
|
"Usage: /dreaming enable light|deep|rem",
|
|
"Usage: /dreaming disable light|deep|rem",
|
|
"",
|
|
includeStatus,
|
|
"",
|
|
"Phases:",
|
|
formatPhaseGuide(),
|
|
].join("\n");
|
|
}
|
|
|
|
export function registerDreamingCommand(api: OpenClawPluginApi): void {
|
|
api.registerCommand({
|
|
name: "dreaming",
|
|
description: "Configure memory dreaming phases and durable promotion behavior.",
|
|
acceptsArgs: true,
|
|
handler: async (ctx) => {
|
|
const args = ctx.args?.trim() ?? "";
|
|
const [firstToken = "", secondToken = ""] = args
|
|
.split(/\s+/)
|
|
.filter(Boolean)
|
|
.map((token) => token.toLowerCase());
|
|
const currentConfig = api.runtime.config.loadConfig();
|
|
|
|
if (
|
|
!firstToken ||
|
|
firstToken === "help" ||
|
|
firstToken === "options" ||
|
|
firstToken === "phases"
|
|
) {
|
|
return { text: formatUsage(formatStatus(currentConfig)) };
|
|
}
|
|
|
|
if (firstToken === "status") {
|
|
return { text: formatStatus(currentConfig) };
|
|
}
|
|
|
|
if (firstToken === "on" || firstToken === "off") {
|
|
const enabled = firstToken === "on";
|
|
const nextConfig = updateDreamingEnabledInConfig(currentConfig, enabled);
|
|
await api.runtime.config.writeConfigFile(nextConfig);
|
|
return {
|
|
text: [
|
|
`Dreaming ${enabled ? "enabled" : "disabled"}.`,
|
|
"",
|
|
formatStatus(nextConfig),
|
|
].join("\n"),
|
|
};
|
|
}
|
|
|
|
const phase = normalizeDreamingPhase(secondToken);
|
|
if ((firstToken === "enable" || firstToken === "disable") && phase) {
|
|
const enabled = firstToken === "enable";
|
|
const nextConfig = updateDreamingPhaseEnabledInConfig(currentConfig, phase, enabled);
|
|
await api.runtime.config.writeConfigFile(nextConfig);
|
|
return {
|
|
text: [
|
|
`${phase.toUpperCase()} phase ${enabled ? "enabled" : "disabled"}.`,
|
|
"",
|
|
formatStatus(nextConfig),
|
|
].join("\n"),
|
|
};
|
|
}
|
|
|
|
return { text: formatUsage(formatStatus(currentConfig)) };
|
|
},
|
|
});
|
|
}
|