121 lines
3.9 KiB
JavaScript
121 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Guard 1: Memory Backend Guard
|
|
* Prevents proliferation beyond the allowed memory backends.
|
|
*
|
|
* → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md
|
|
* → Core: memory-core | Legacy: memory-wiki
|
|
*
|
|
* Allowed plugins (kind="memory" or has memory tools):
|
|
* - memory-core (primary — Core §2)
|
|
* - active-memory (recall injector — Core §2)
|
|
* - memory-wiki (legacy, allowed for now — Legacy §4)
|
|
* - Builtin/QMD (internal engine, not a plugin)
|
|
*
|
|
* FAIL: any new memory plugin appears
|
|
* PASS: within allowed list
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { homedir } from "node:os";
|
|
|
|
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || "/opt/homebrew/lib/node_modules/openclaw";
|
|
const EXTENSIONS_DIR = join(OPENCLAW_HOME, "dist", "extensions");
|
|
|
|
// Allowed memory plugin IDs
|
|
const ALLOWED_MEMORY_PLUGINS = new Set([
|
|
"memory-core",
|
|
"active-memory",
|
|
"memory-wiki",
|
|
]);
|
|
|
|
// Plugin IDs that have memory contracts but are not "kind: memory"
|
|
const KNOWN_MEMORY_RELATED = new Set([
|
|
"memory-lancedb", // legacy, being deprecated
|
|
]);
|
|
|
|
function guardMemoryBackends() {
|
|
const issues = [];
|
|
const found = [];
|
|
|
|
if (!existsSync(EXTENSIONS_DIR)) {
|
|
issues.push({ level: "ERROR", msg: `Extensions dir not found: ${EXTENSIONS_DIR}` });
|
|
return { pass: false, found, issues };
|
|
}
|
|
|
|
const extDirs = readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
|
.filter(d => d.isDirectory())
|
|
.map(d => d.name);
|
|
|
|
for (const extDir of extDirs) {
|
|
const pluginJsonPath = join(EXTENSIONS_DIR, extDir, "openclaw.plugin.json");
|
|
if (!existsSync(pluginJsonPath)) continue;
|
|
|
|
try {
|
|
const plugin = JSON.parse(readFileSync(pluginJsonPath, "utf8"));
|
|
const pluginId = plugin.id || extDir;
|
|
const kind = plugin.kind || "";
|
|
const contracts = plugin.contracts || {};
|
|
const tools = contracts.tools || [];
|
|
const memoryTools = tools.filter(t => t.startsWith("memory_") || t.startsWith("wiki_"));
|
|
|
|
const isMemoryPlugin =
|
|
kind === "memory" ||
|
|
tools.includes("memory_search") ||
|
|
tools.includes("memory_get") ||
|
|
memoryTools.length >= 2;
|
|
|
|
if (isMemoryPlugin) {
|
|
found.push({ id: pluginId, kind, memoryTools });
|
|
|
|
if (!ALLOWED_MEMORY_PLUGINS.has(pluginId) && !KNOWN_MEMORY_RELATED.has(pluginId)) {
|
|
issues.push({
|
|
level: "FAIL",
|
|
msg: `UNEXPECTED memory plugin: ${pluginId} (kind=${kind}, tools=${memoryTools.join(",")})`
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
issues.push({ level: "WARN", msg: `Could not parse ${pluginJsonPath}: ${e.message}` });
|
|
}
|
|
}
|
|
|
|
const pass = issues.filter(i => i.level === "FAIL").length === 0;
|
|
return { pass, found, issues };
|
|
}
|
|
|
|
// Run
|
|
const result = guardMemoryBackends();
|
|
|
|
console.log("");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log(" Guard 1: Memory Backend Guard");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log("");
|
|
|
|
if (result.found.length === 0) {
|
|
console.log(" No memory plugins found.");
|
|
} else {
|
|
console.log(` Memory plugins found: ${result.found.length}`);
|
|
for (const f of result.found) {
|
|
const status = ALLOWED_MEMORY_PLUGINS.has(f.id) ? "✓" :
|
|
KNOWN_MEMORY_RELATED.has(f.id) ? "⚠ (legacy)" : "✗ UNEXPECTED";
|
|
console.log(` ${status} ${f.id} (kind="${f.kind}", tools: ${f.memoryTools.join(", ")})`);
|
|
}
|
|
}
|
|
|
|
for (const issue of result.issues) {
|
|
console.log(` [${issue.level}] ${issue.msg}`);
|
|
}
|
|
|
|
if (result.pass) {
|
|
console.log("");
|
|
console.log(" ✓ PASS: memory backend count within allowed list");
|
|
process.exit(0);
|
|
} else {
|
|
console.log("");
|
|
console.log(" ✗ FAIL: unexpected memory backend(s) detected");
|
|
process.exit(1);
|
|
}
|