Files
openclaw/extensions/active-memory/doctor-contract-api.ts
Peter Steinberger 8b0735e89f refactor(memory)!: remove the QMD backend; builtin is the only memory engine (#120936)
* refactor(memory): remove qmd backend

Make builtin the sole memory-core engine, rename the retained session helper barrel, retire QMD config with doctor migrations, and remove QMD runtime/UI/policy surfaces.

* docs(memory): remove qmd backend guidance

Delete the QMD concept page, rewrite memory documentation for builtin retrieval, and remove QMD from navigation and taxonomy source.

* refactor(memory): remove qmd-only leftovers

* refactor(memory): finish qmd integration cleanup

* build(deps): align root string-width types

* build(deps): model root string-width tooling

* refactor(memory): align qmd removal ui and docs

* fix(memory): preserve qmd external paths in doctor

* test(memory): remove obsolete backend probe case

* test(plugin-sdk): refresh private type baseline
2026-08-09 03:05:47 -07:00

121 lines
4.3 KiB
TypeScript

/**
* Doctor migration contract for Active Memory state. It moves legacy per-session
* toggle JSON into the plugin state keyed store used by current runtimes.
*/
import crypto from "node:crypto";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import {
asObjectRecord,
defineLegacyJsonStateMigration,
type PluginDoctorStateMigration,
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
type ActiveMemoryToggleEntry = {
sessionKey: string;
disabled: boolean;
updatedAt: number;
};
const TOGGLE_STATE_FILE = "session-toggles.json";
const SESSION_TOGGLES_NAMESPACE = "session-toggles";
const MAX_TOGGLE_ENTRIES = 10_000;
const RETIRED_QMD_CONFIG_PATH = ["plugins", "entries", "active-memory", "config", "qmd"];
/** Retired Active Memory QMD override detected before strict manifest validation. */
export const legacyConfigRules = [
{
path: RETIRED_QMD_CONFIG_PATH,
message:
'plugins.entries.active-memory.config.qmd is retired because the QMD memory backend was removed. Run "openclaw doctor --fix".',
},
];
/** Removes the retired plugin-owned QMD override. */
export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): {
config: OpenClawConfig;
changes: string[];
} {
const entry = asObjectRecord(cfg.plugins?.entries?.["active-memory"]);
const pluginConfig = asObjectRecord(entry?.config);
if (!pluginConfig || !Object.hasOwn(pluginConfig, "qmd")) {
return { config: cfg, changes: [] };
}
const nextConfig = structuredClone(cfg);
const nextEntry = asObjectRecord(nextConfig.plugins?.entries?.["active-memory"]);
const nextPluginConfig = asObjectRecord(nextEntry?.config);
if (!nextPluginConfig) {
return { config: cfg, changes: [] };
}
delete nextPluginConfig.qmd;
return {
config: nextConfig,
changes: ["Removed retired Active Memory QMD search-mode configuration."],
};
}
function resolveToggleStatePath(stateDir: string): string {
return path.join(stateDir, "plugins", "active-memory", TOGGLE_STATE_FILE);
}
function activeMemoryToggleKey(sessionKey: string): string {
return crypto.createHash("sha256").update(sessionKey, "utf8").digest("hex");
}
function normalizeLegacyUpdatedAt(value: unknown): number {
return typeof value === "number" && Number.isFinite(value) ? value : Date.now();
}
function parseLegacyToggleEntries(parsed: unknown): ActiveMemoryToggleEntry[] | null {
if (!parsed || typeof parsed !== "object") {
return null;
}
const sessions = (parsed as { sessions?: unknown }).sessions;
if (!sessions || typeof sessions !== "object" || Array.isArray(sessions)) {
return null;
}
const entries: ActiveMemoryToggleEntry[] = [];
for (const [sessionKey, value] of Object.entries(sessions)) {
if (!sessionKey.trim() || !value || typeof value !== "object" || Array.isArray(value)) {
continue;
}
if ((value as { disabled?: unknown }).disabled !== true) {
continue;
}
const updatedAt = normalizeLegacyUpdatedAt((value as { updatedAt?: unknown }).updatedAt);
entries.push({ sessionKey, disabled: true, updatedAt });
}
return entries;
}
/** State migrations exposed to OpenClaw doctor for Active Memory. */
export const stateMigrations: PluginDoctorStateMigration[] = [
defineLegacyJsonStateMigration<ActiveMemoryToggleEntry[]>({
id: "active-memory-session-toggles-json-to-plugin-state",
label: "Active Memory session toggles",
resolvePath: resolveToggleStatePath,
parse: parseLegacyToggleEntries,
namespace: SESSION_TOGGLES_NAMESPACE,
maxEntries: MAX_TOGGLE_ENTRIES,
capacityPrecheck: {
warning: ({ available, missing }) =>
`Skipped Active Memory session toggle migration because plugin state has room for ${available} of ${missing} missing entries; left legacy source in place`,
},
describeEntries: (entries) => ({
preview: [
`- Active Memory session toggles: ${entries.length} ${entries.length === 1 ? "entry" : "entries"} -> plugin state (${SESSION_TOGGLES_NAMESPACE})`,
],
change: ({ imported }) =>
imported > 0
? `Migrated ${imported} Active Memory session toggle ${imported === 1 ? "entry" : "entries"} -> plugin state`
: null,
}),
toRows: (entries) =>
entries.map((entry) => ({
key: activeMemoryToggleKey(entry.sessionKey),
value: entry,
})),
}),
];