agents / dynamicTools.js
"use strict";
/**
* Dynamic tool creation — Agent Zero's self-evolving capability.
*
* Tools are Python scripts saved to .agent_tools/<name>.py in the workspace.
* Each script receives JSON arguments via stdin and prints its result to stdout.
*
* All tools use the single Python interpreter configured in plugin settings.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTool = createTool;
exports.callTool = callTool;
exports.listTools = listTools;
exports.deleteTool = deleteTool;
const promises_1 = require("fs/promises");
const path_1 = require("path");
const fs_1 = require("fs");
const sandbox_1 = require("../sandbox");
const TOOLS_DIR_NAME = ".agent_tools";
const INDEX_FILE = "__index__.json";
function toolsDir(workspace) {
return (0, path_1.join)(workspace, TOOLS_DIR_NAME);
}
async function loadIndex(workspace) {
try {
return JSON.parse(await (0, promises_1.readFile)((0, path_1.join)(toolsDir(workspace), INDEX_FILE), "utf-8"));
}
catch {
return { tools: [] };
}
}
async function saveIndex(workspace, index) {
const dir = toolsDir(workspace);
await (0, promises_1.mkdir)(dir, { recursive: true });
await (0, promises_1.writeFile)((0, path_1.join)(dir, INDEX_FILE), JSON.stringify(index, null, 2), "utf-8");
}
function sanitizeName(name) {
return name.replace(/[^a-zA-Z0-9_]/g, "_").slice(0, 64);
}
// ── Public API ─────────────────────────────────────────────────────────────
async function createTool(workspace, name, description, argsSchema, pythonCode) {
const safe = sanitizeName(name);
if (!safe)
return "Invalid tool name.";
const dir = toolsDir(workspace);
await (0, promises_1.mkdir)(dir, { recursive: true });
// Wrap user code so it always reads args from stdin as JSON
const wrapped = `#!/usr/bin/env python3
"""Dynamic tool: ${safe}
${description}
"""
import json, sys
def main(args: dict):
${pythonCode.split("\n").map(l => " " + l).join("\n")}
if __name__ == "__main__":
raw = sys.stdin.read().strip()
args = json.loads(raw) if raw else {}
result = main(args)
if result is not None:
print(result if isinstance(result, str) else json.dumps(result))
`;
const scriptPath = (0, path_1.join)(dir, `${safe}.py`);
await (0, promises_1.writeFile)(scriptPath, wrapped, "utf-8");
const index = await loadIndex(workspace);
const existing = index.tools.findIndex(t => t.name === safe);
const meta = { name: safe, description, argsSchema, created: new Date().toISOString() };
if (existing >= 0) {
index.tools[existing] = meta;
}
else {
index.tools.push(meta);
}
await saveIndex(workspace, index);
return `Created tool "${safe}" at ${scriptPath}`;
}
async function callTool(workspace, name, argsJson, pythonBin, timeout) {
const safe = sanitizeName(name);
const scriptPath = (0, path_1.join)(toolsDir(workspace), `${safe}.py`);
if (!(0, fs_1.existsSync)(scriptPath)) {
const index = await loadIndex(workspace);
const names = index.tools.map(t => t.name).join(", ") || "(none)";
return `Tool "${name}" not found. Available tools: ${names}`;
}
let normalizedArgs = "{}";
if (argsJson.trim()) {
try {
normalizedArgs = JSON.stringify(JSON.parse(argsJson));
}
catch {
return `Invalid JSON args: ${argsJson}`;
}
}
const r = await (0, sandbox_1.run)(pythonBin, [scriptPath], {
stdin: normalizedArgs,
cwd: workspace,
timeout,
});
if (!r.success && !r.stdout) {
return `Tool "${name}" failed (exit ${r.code}):\n${r.stderr}`;
}
return r.stdout || r.stderr || "(no output)";
}
async function listTools(workspace) {
const index = await loadIndex(workspace);
if (!index.tools.length)
return "No dynamic tools created yet.";
return JSON.stringify(index.tools.map(t => ({ name: t.name, description: t.description, args: t.argsSchema, created: t.created })), null, 2);
}
async function deleteTool(workspace, name) {
const safe = sanitizeName(name);
const scriptPath = (0, path_1.join)(toolsDir(workspace), `${safe}.py`);
if (!(0, fs_1.existsSync)(scriptPath))
return `Tool "${name}" not found.`;
await (0, promises_1.unlink)(scriptPath);
const index = await loadIndex(workspace);
index.tools = index.tools.filter(t => t.name !== safe);
await saveIndex(workspace, index);
return `Deleted tool "${safe}".`;
}