#!/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); }