190 lines
6.1 KiB
JavaScript
190 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Guard 2: Agent Runtime Core Guard
|
|
* Prevents proliferation beyond the 3 allowed agent runtimes.
|
|
*
|
|
* → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md
|
|
* → Core: embedded-agent-runner, acp | Experimental: codex-supervisor
|
|
*
|
|
* Allowed agent runtimes (entry points):
|
|
* - embedded-agent (built into OpenClaw — Core §2)
|
|
* - codex (codex supervisor/app-server harness — Experimental §5)
|
|
* - acp (ACP agent — Core §2)
|
|
*
|
|
* RUNTIME UTILITY FILES (e.g. auth-profiles.runtime.js) are NOT new runtimes.
|
|
* Only AGENT ENTRY POINT modules count as runtime cores.
|
|
*
|
|
* FAIL: a 4th agent runtime entry point appears
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || "/opt/homebrew/lib/node_modules/openclaw";
|
|
const DIST_DIR = join(OPENCLAW_HOME, "dist");
|
|
|
|
// Known agent runtime core entry points
|
|
const KNOWN_RUNTIME_ENTRIES = new Set([
|
|
"embedded-agent", // Embedded agent runner
|
|
"codex-supervisor", // Codex app-server harness
|
|
"acp", // ACP agent directory
|
|
]);
|
|
|
|
// Known runtime UTILITY files (not new runtimes)
|
|
const RUNTIME_UTILITY_PREFIXES = [
|
|
"auth-profiles",
|
|
"models-config",
|
|
"model-catalog",
|
|
"agent-bundle-mcp",
|
|
"runtime-",
|
|
"runtime.",
|
|
];
|
|
|
|
function isRuntimeUtility(filename) {
|
|
const name = filename.replace(/\.js$/, "").replace(/\.d\.ts$/, "");
|
|
return RUNTIME_UTILITY_PREFIXES.some(p => name.startsWith(p) || name.includes(".runtime"));
|
|
}
|
|
|
|
function guardRuntimeCores() {
|
|
const issues = [];
|
|
const foundEntries = [];
|
|
const utilities = [];
|
|
|
|
if (!existsSync(DIST_DIR)) {
|
|
issues.push({ level: "ERROR", msg: `Dist dir not found: ${DIST_DIR}` });
|
|
return { pass: false, foundEntries, issues };
|
|
}
|
|
|
|
// 1. Check extensions for agent runtime plugins
|
|
const extensionsDir = join(DIST_DIR, "extensions");
|
|
if (existsSync(extensionsDir)) {
|
|
const extDirs = readdirSync(extensionsDir, { withFileTypes: true })
|
|
.filter(d => d.isDirectory());
|
|
|
|
for (const extDir of extDirs) {
|
|
const pluginJsonPath = join(extensionsDir, extDir.name, "openclaw.plugin.json");
|
|
if (!existsSync(pluginJsonPath)) continue;
|
|
|
|
try {
|
|
const plugin = JSON.parse(readFileSync(pluginJsonPath, "utf8"));
|
|
const id = plugin.id || extDir.name;
|
|
|
|
// Check if this plugin provides agent runtime capabilities
|
|
const contracts = plugin.contracts || {};
|
|
const tools = contracts.tools || [];
|
|
const hasRuntimeTools = tools.some(t =>
|
|
t.includes("codex_session") ||
|
|
t.includes("agent_") ||
|
|
t.includes("acp_")
|
|
);
|
|
|
|
if (hasRuntimeTools) {
|
|
foundEntries.push({ name: id, source: "plugin.json", type: "extension" });
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
// 2. Check for embedded-agent directory
|
|
const agentsDir = join(DIST_DIR, "agents");
|
|
if (existsSync(agentsDir)) {
|
|
const agentSubDirs = readdirSync(agentsDir, { withFileTypes: true })
|
|
.filter(d => d.isDirectory() && d.name.includes("agent"));
|
|
for (const d of agentSubDirs) {
|
|
foundEntries.push({ name: d.name, source: `dist/agents/${d.name}`, type: "core" });
|
|
}
|
|
}
|
|
|
|
// 3. Check for ACP directory
|
|
const acpDir = join(DIST_DIR, "acp");
|
|
if (existsSync(acpDir)) {
|
|
foundEntries.push({ name: "acp", source: "dist/acp/", type: "core" });
|
|
}
|
|
|
|
// 4. Categorize: entries vs utilities
|
|
const agentRuntimeEntries = foundEntries.filter(e => {
|
|
const name = e.name;
|
|
return KNOWN_RUNTIME_ENTRIES.has(name) ||
|
|
name.includes("embedded-agent") ||
|
|
name.includes("codex") ||
|
|
name === "acp";
|
|
});
|
|
|
|
const unknownEntries = foundEntries.filter(e => !agentRuntimeEntries.includes(e));
|
|
|
|
console.log(` Agent runtime entries found: ${agentRuntimeEntries.length}`);
|
|
console.log(` Allowed maximum: 3 (embedded, codex, acp)`);
|
|
|
|
for (const e of agentRuntimeEntries) {
|
|
const known = KNOWN_RUNTIME_ENTRIES.has(e.name) ||
|
|
e.name.includes("embedded-agent") ||
|
|
e.name.includes("codex") ||
|
|
e.name === "acp";
|
|
const status = known ? "✓" : "?";
|
|
console.log(` ${status} ${e.name} (${e.source}, type=${e.type})`);
|
|
}
|
|
|
|
// Check for unknown entries
|
|
for (const e of unknownEntries) {
|
|
if (isRuntimeUtility(e.name)) {
|
|
utilities.push(e);
|
|
console.log(` ○ ${e.name} (utility — not a runtime core)`);
|
|
} else {
|
|
issues.push({
|
|
level: "FAIL",
|
|
msg: `UNEXPECTED agent runtime entry: ${e.name} (${e.source})`
|
|
});
|
|
}
|
|
}
|
|
|
|
// Count distinct runtime types
|
|
const distinctEntries = new Set(
|
|
agentRuntimeEntries.map(e => {
|
|
if (e.name.includes("embedded-agent")) return "embedded";
|
|
if (e.name.includes("codex")) return "codex";
|
|
if (e.name === "acp") return "acp";
|
|
return e.name;
|
|
})
|
|
);
|
|
|
|
if (distinctEntries.size > 3) {
|
|
issues.push({
|
|
level: "FAIL",
|
|
msg: `Runtime entry count ${distinctEntries.size} > allowed maximum 3`
|
|
});
|
|
}
|
|
|
|
const pass = issues.filter(i => i.level === "FAIL").length === 0;
|
|
|
|
if (utilities.length > 0) {
|
|
console.log(` Runtime utility files: ${utilities.length} (not runtime cores)`);
|
|
}
|
|
|
|
return { pass, foundEntries: agentRuntimeEntries, issues, utilities };
|
|
}
|
|
|
|
// Run
|
|
const result = guardRuntimeCores();
|
|
|
|
console.log("");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log(" Guard 2: Agent Runtime Core Guard");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log("");
|
|
|
|
for (const issue of result.issues) {
|
|
console.log(` [${issue.level}] ${issue.msg}`);
|
|
}
|
|
|
|
if (result.pass) {
|
|
console.log("");
|
|
console.log(" ✓ PASS: no unexpected agent runtime core detected");
|
|
console.log(` Allowed limit: 3 (embedded, codex, acp)`);
|
|
console.log(` Found: ${result.foundEntries.length} runtime entries`);
|
|
process.exit(0);
|
|
} else {
|
|
console.log("");
|
|
console.log(" ✗ FAIL: unexpected runtime core(s) detected");
|
|
process.exit(1);
|
|
}
|