UNPKG

naisys

Version:

Node.js Autonomous Intelligence System

226 lines 8.41 kB
import { spawn } from "child_process"; import * as fs from "fs"; import yaml from "js-yaml"; import path from "path"; import table from "text-table"; import * as config from "../config.js"; import { agentNames } from "../utils/agentNames.js"; import * as inputMode from "../utils/inputMode.js"; import { InputMode } from "../utils/inputMode.js"; import * as output from "../utils/output.js"; import { OutputColor } from "../utils/output.js"; import * as pathService from "../utils/pathService.js"; import { NaisysPath } from "../utils/pathService.js"; import { getTokenCount, shuffle } from "../utils/utilities.js"; import * as llmail from "./llmail.js"; let _nextAgentId = 1; const _subagents = []; _init(); function _init() { // Load subagents for user from file system const subagentDir = _getSubagentDir(); const subagentHostDir = subagentDir.toHostPath(); if (!fs.existsSync(subagentHostDir)) { return; } const subagentFiles = fs.readdirSync(subagentHostDir); // Iterate files for (const subagentFile of subagentFiles) { const agentHostPath = path.join(subagentHostDir, subagentFile); const subagentYaml = fs.readFileSync(agentHostPath, "utf8"); const subagentConfig = yaml.load(subagentYaml); // Add to subagents _subagents.push({ id: _nextAgentId++, agentName: subagentConfig.username, agentPath: new NaisysPath(agentHostPath), taskDescription: subagentConfig.taskDescription || "No task description", log: "", status: "stopped", tokensSpent: 0, }); } } export async function handleCommand(args) { const argParams = args.split(" "); if (!argParams[0]) { argParams[0] = "help"; } let errorText = ""; switch (argParams[0]) { case "help": { let helpOutput = `subagent <command> list: Lists all subagents create "<agent title>" "<agent task and guidance>": Creates a new agent. Treat it like a new hire. Include as much detail and guidace as possible. stop <id>: Stops the agent with the given task id start <id>: Starts a stopped agent with the given task id`; if (inputMode.current == InputMode.Debug) { helpOutput += `\n flush <id>: Debug only command to show the agent's context log`; } helpOutput += `\n\n* You can have up to ${config.agent.subagentMax} subagents running at a time. Use llmail to communicate with subagents by name.`; return helpOutput; } case "list": { return table([ ["ID", "Status", "Name", "Task", "Tokens Spent"], ..._subagents.map((p) => [ p.id, p.status, p.agentName, p.taskDescription.substring(0, 70), p.tokensSpent, ]), ], { hsep: " | " }); } case "create": { const newParams = argParams.slice(1).join(" ").split('"'); const title = newParams[1]; const task = newParams[3]; // Validate title and task set if (!title || !task) { errorText = "See valid 'create' syntax below:\n"; break; } return await _createAgent(title, task); } case "start": { const subagentId = parseInt(argParams[1]); return _startAgent(subagentId); } case "stop": { const subagentId = parseInt(argParams[1]); return _stopAgent(subagentId); } case "flush": { const subagentId = parseInt(argParams[1]); _debugFlushContext(subagentId); return ""; } default: { errorText = "Error, unknown command. See valid commands below:\n"; } } return errorText + (await handleCommand("help")); } export function getRunningSubagentNames() { return _subagents .filter((p) => p.status === "running") .map((p) => p.agentName); } /** Return list of subagents with unread context */ export function unreadContextSummary() { const unreadAgents = _subagents.filter((p) => p.log.length > 0); if (unreadAgents.length === 0) { return ""; } output.comment("New Subagent Output: " + unreadAgents .map((p) => `${p.id}:${p.agentName}, ${getTokenCount(p.log)} tokens`) .join(" | ")); } async function _createAgent(title, taskDescription) { // Get available username const usernames = await llmail.getAllUserNames(); let agentName = ""; const shuffledNames = shuffle(agentNames); for (const name of shuffledNames) { if (!usernames.includes(name)) { agentName = name; break; } } if (!agentName) { throw "No available usernames for subagents"; } // Generate agent yaml const agentYaml = yaml.dump({ ...config.agent, username: agentName, title, agentPrompt: `You are \${agent.username} a \${agent.title} with the job of helping out the \${agent.leadAgent} with what they want to do.\n` + `Task Description: \${agent.taskDescription}\n` + `Perform the above task and/or wait for messages from \${agent.leadAgent} and respond to them.`, wakeOnMessage: true, initialCommands: ["llmail users", "llmail help"], leadAgent: config.agent.username, taskDescription, }); // write agent yaml to file const subagentDir = _getSubagentDir(); const agentHostPath = path.join(subagentDir.toHostPath(), `${agentName}.yaml`); const agentPath = new NaisysPath(agentHostPath); pathService.ensureFileDirExists(agentPath); fs.writeFileSync(agentHostPath, agentYaml); const id = _nextAgentId++; _subagents.push({ id, agentName, agentPath, taskDescription, log: "", status: "stopped", tokensSpent: 0, }); return "Subagent Created\n" + _startAgent(id); } function _startAgent(id) { const subagent = _subagents.find((p) => p.id === id); if (!subagent) { throw `Subagent ${id} not found`; } if (subagent.status === "running") { throw `Subagent ${id} is already running`; } // Check that max sub agents aren't already started const runningSubagents = _subagents.filter((p) => p.status === "running"); if (runningSubagents.length >= (config.agent.subagentMax || 0)) { throw `Max subagents already running`; } // Start subagent const installPath = pathService.getInstallPath(); const naisysJsPath = path.join(installPath.getHostPath(), "dist/naisys.js"); subagent.process = spawn("node", [naisysJsPath, subagent.agentPath.toHostPath()], { stdio: "pipe", }); subagent.process.on("spawn", () => { subagent.status = "running"; subagent.log += `SUBAGENT ${id} SPAWNED\n`; }); subagent.process.stdout.on("data", (data) => { const dataStr = data.toString(); subagent.log += dataStr; subagent.tokensSpent += getTokenCount(dataStr); }); subagent.process.stderr.on("data", (data) => { output.error(`SUBAGENT ${id} ERROR: ${data}`); }); subagent.process.on("close", () => { subagent.log += `\nSUBAGENT ${id} CLOSED`; subagent.status = "stopped"; }); return `Subagent ID: ${id} Started\nUse llmail to communicate with the subagent '${subagent.agentName}'`; } function _stopAgent(id) { const subagent = _subagents.find((p) => p.id === id); if (!subagent) { throw `Subagent ${id} not found`; } if (subagent.status === "stopped") { throw `Subagent ${id} is already stopped`; } subagent.process?.kill(); return `Subagent ${id} stopped`; } /** Only used in debug mode, not by LLM */ function _debugFlushContext(subagentId) { const subagent = _subagents.find((p) => p.id === subagentId); if (!subagent) { throw `Subagent ${subagentId} not found`; } output.write(subagent.log, OutputColor.subagent); subagent.log = ""; } function _getSubagentDir() { return new NaisysPath(`${config.naisysFolder}/agent-data/${config.agent.username}/subagents`); } //# sourceMappingURL=subagent.js.map