144 lines
5.0 KiB
JavaScript
144 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Guard 3: Tool Path Guard
|
|
* Records current tool call entry points. Soft warning — no hard fail yet.
|
|
*
|
|
* → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md
|
|
* → Adapter §3.2: Tool Adapters
|
|
*
|
|
* ToolCore not yet implemented — this guard inventories the current state
|
|
* and will FAIL only if a NEW unknown tool path appears.
|
|
*
|
|
* Known tool paths:
|
|
* - native tool (dist/commands/*)
|
|
* - MCP tool (dist/mcp/*)
|
|
* - plugin tool (dist/extensions plugin.json contracts.tools)
|
|
* - codex tool (dist/extensions/codex-supervisor/*)
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, existsSync } 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");
|
|
const EXTENSIONS_DIR = join(DIST_DIR, "extensions");
|
|
|
|
function guardToolPaths() {
|
|
const issues = [];
|
|
const inventory = { native: [], mcp: [], plugin: [], codex: [], unknown: [] };
|
|
const knownToolPaths = new Set([
|
|
"native", "mcp", "plugin", "codex"
|
|
]);
|
|
|
|
// 1. Native tools — look for tool registrations in commands
|
|
const commandsDir = join(DIST_DIR, "commands");
|
|
if (existsSync(commandsDir)) {
|
|
try {
|
|
const entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
if (entry.isDirectory()) {
|
|
inventory.native.push(`commands/${entry.name}`);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
issues.push({ level: "WARN", msg: `Could not scan commands dir: ${e.message}` });
|
|
}
|
|
}
|
|
|
|
// 2. MCP tools
|
|
const mcpDir = join(DIST_DIR, "mcp");
|
|
if (existsSync(mcpDir)) {
|
|
try {
|
|
const files = readdirSync(mcpDir).filter(f => f.endsWith(".js"));
|
|
inventory.mcp.push(...files.map(f => `mcp/${f}`));
|
|
} catch (e) {
|
|
issues.push({ level: "WARN", msg: `Could not scan mcp dir: ${e.message}` });
|
|
}
|
|
}
|
|
|
|
// 3. Plugin tools (from openclaw.plugin.json contracts)
|
|
if (existsSync(EXTENSIONS_DIR)) {
|
|
try {
|
|
const extDirs = readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
|
.filter(d => d.isDirectory());
|
|
|
|
for (const extDir of extDirs) {
|
|
const pluginJsonPath = join(EXTENSIONS_DIR, extDir.name, "openclaw.plugin.json");
|
|
if (!existsSync(pluginJsonPath)) continue;
|
|
|
|
try {
|
|
const plugin = JSON.parse(readFileSync(pluginJsonPath, "utf8"));
|
|
const tools = plugin.contracts?.tools || [];
|
|
|
|
if (tools.length > 0) {
|
|
// Categorize
|
|
if (extDir.name.includes("codex") || extDir.name.includes("supervisor")) {
|
|
inventory.codex.push(`${extDir.name}: [${tools.join(", ")}]`);
|
|
} else {
|
|
inventory.plugin.push(`${extDir.name}: [${tools.join(", ")}]`);
|
|
}
|
|
}
|
|
} catch {}
|
|
}
|
|
} catch (e) {
|
|
issues.push({ level: "WARN", msg: `Could not scan extensions: ${e.message}` });
|
|
}
|
|
}
|
|
|
|
// 4. Check for unknown tool paths in key files
|
|
const keyFiles = readdirSync(DIST_DIR).filter(f =>
|
|
f.endsWith(".js") && (f.includes("tool") || f.includes("command") || f.includes("mcp"))
|
|
);
|
|
for (const f of keyFiles) {
|
|
const name = f.replace(/\.js$/, "").replace(/-[A-Za-z0-9]{8}$/, "");
|
|
// Categorize based on naming
|
|
if (name.includes("mcp") || name.includes("codex-mcp")) {
|
|
if (!inventory.mcp.some(t => t.includes(name.substring(0, 10)))) {
|
|
inventory.mcp.push(`dist/${f}`);
|
|
}
|
|
} else if (name.includes("tool")) {
|
|
if (!inventory.native.some(t => t.includes(name.substring(0, 10)))) {
|
|
inventory.native.push(`dist/${f}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Report
|
|
console.log("");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log(" Guard 3: Tool Path Guard");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log("");
|
|
|
|
console.log(" Tool Path Inventory:");
|
|
console.log(` Native tools: ${inventory.native.length} entries`);
|
|
console.log(` MCP tools: ${inventory.mcp.length} entries`);
|
|
console.log(` Plugin tools: ${inventory.plugin.length} plugins`);
|
|
console.log(` Codex tools: ${inventory.codex.length} plugins`);
|
|
|
|
if (inventory.plugin.length > 0) {
|
|
console.log("\n Plugin tool contracts:");
|
|
for (const p of inventory.plugin) {
|
|
console.log(` • ${p}`);
|
|
}
|
|
}
|
|
|
|
if (inventory.codex.length > 0) {
|
|
console.log("\n Codex tool contracts:");
|
|
for (const p of inventory.codex) {
|
|
console.log(` • ${p}`);
|
|
}
|
|
}
|
|
|
|
// This guard is WARN-only for now (ToolCore not yet implemented)
|
|
console.log("");
|
|
console.log(" ⚠ WARN: ToolCore not yet enforced");
|
|
console.log(" This guard currently inventories tool paths.");
|
|
console.log(" After PR-8/PR-9 (ToolCore), it will enforce all tools go through ToolExecutor.");
|
|
|
|
// No hard fail — informational only at this stage
|
|
process.exit(0);
|
|
}
|
|
|
|
guardToolPaths();
|