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