diff --git a/.gitignore b/.gitignore index 343d841..e1bc9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .vscode/ +.fslckout +._* .idea/ node_modules/ package-lock.json diff --git a/andy.json b/andy.json index 97b45b4..15fb719 100644 --- a/andy.json +++ b/andy.json @@ -1,6 +1,6 @@ { "name": "andy", - "model": "gpt-4o-mini" + "model": "qwen-max" } \ No newline at end of file diff --git a/settings.js b/settings.js index b782097..9bd47cd 100644 --- a/settings.js +++ b/settings.js @@ -45,6 +45,7 @@ const settings = { "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') "chat_bot_messages": true, // publicly chat messages to other bots "log_all_prompts": false, // log ALL prompts to file + // "plugins" : ["Dance"], // plugin will be loaded if and only if it's name appears here } // these environment variables override certain settings diff --git a/src/agent/._action_manager.js b/src/agent/._action_manager.js new file mode 100644 index 0000000..f36de6e Binary files /dev/null and b/src/agent/._action_manager.js differ diff --git a/src/agent/agent.js b/src/agent/agent.js index 3cd671b..ba57993 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -7,6 +7,7 @@ import { initBot } from '../utils/mcdata.js'; import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction, blacklistCommands } from './commands/index.js'; import { ActionManager } from './action_manager.js'; import { NPCContoller } from './npc/controller.js'; +import { PluginManager } from './plugin.js'; import { MemoryBank } from './memory_bank.js'; import { SelfPrompter } from './self_prompter.js'; import convoManager from './conversation.js'; @@ -39,6 +40,8 @@ export class Agent { this.coder = new Coder(this); console.log('Initializing npc controller...'); this.npc = new NPCContoller(this); + console.log('Initializing plugin manager...'); + this.plugin = new PluginManager(this); console.log('Initializing memory bank...'); this.memory_bank = new MemoryBank(); console.log('Initializing self prompter...'); @@ -457,6 +460,8 @@ export class Agent { // Init NPC controller this.npc.init(); + // Init plugins manager + this.plugin.init(); // This update loop ensures that each update() is called one at a time, even if it takes longer than the interval const INTERVAL = 300; diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index b2b3ccb..e093713 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -2,8 +2,7 @@ import * as skills from '../library/skills.js'; import settings from '../../../settings.js'; import convoManager from '../conversation.js'; - -function runAsAction (actionFn, resume = false, timeout = -1) { +export function runAsAction (actionFn, resume = false, timeout = -1) { let actionLabel = null; // Will be set on first use const wrappedAction = async function (agent, ...args) { diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index 44caf6a..011d9a7 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -26,6 +26,18 @@ export function blacklistCommands(commands) { } } +export function addPluginActions(plugin, actions) { + for (let action of actions) { + if (commandMap[action.name]) { + console.log(`Command already exists. Can't add ${action.name} from plugin ${plugin}.`) + } else { + commandMap[action.name] = action; + commandList.push(action); + actionsList.push(action); + } + } +} + const commandRegex = /!(\w+)(?:\(((?:-?\d+(?:\.\d+)?|true|false|"[^"]*")(?:\s*,\s*(?:-?\d+(?:\.\d+)?|true|false|"[^"]*"))*)\))?/ const argRegex = /-?\d+(?:\.\d+)?|true|false|"[^"]*"/g; diff --git a/src/agent/plugin.js b/src/agent/plugin.js new file mode 100644 index 0000000..cd67618 --- /dev/null +++ b/src/agent/plugin.js @@ -0,0 +1,59 @@ + +import { readdirSync, readFileSync } from 'fs'; +import { join, relative, isAbsolute } from 'path'; +import { pathToFileURL } from 'url'; +import settings from '../../settings.js'; +import { addPluginActions } from './commands/index.js'; + +export class PluginManager { + constructor(agent) { + this.agent = agent; + this.plugins = {}; + } + + init() { + this.importPlugins() + .then((plugins) => { + this.plugins = plugins; + for (let plugin in this.plugins) { + addPluginActions(plugin, this.plugins[plugin].getPluginActions()); + } + console.log("Load plugins:", Object.keys(this.plugins).join(", ")); + }) + .catch((error) => { + console.error("Error importing plugins:", error); + }); + } + + async importPlugin(dir, name) { + let path = join(dir, name, "main.js"); + let instance = null; + try { + const plugin = await import(pathToFileURL(path).href); + if (plugin.PluginInstance) { + instance = new plugin.PluginInstance(this.agent); + instance.init(); + } else { + console.error(`Can't find PluginInstance in ${path}.`); + } + } catch (error) { + console.error(`Error import plugin ${path}:`, error); + } + return instance; + } + + async importPlugins(dir = "src/plugins") { + let plugins = {}; + try { + for (let file of readdirSync(dir, { withFileTypes: true })) { + if (settings.plugins && settings.plugins.includes(file.name) && file.isDirectory && !file.name.startsWith('.')) { + let instance = await this.importPlugin(dir, file.name); + plugins[file.name] = instance; + } + } + } catch (error) { + console.error(`Error importing plugins in ${dir}:`, error); + } + return plugins; + } +} \ No newline at end of file diff --git a/src/plugins/Dance/main.js b/src/plugins/Dance/main.js new file mode 100644 index 0000000..b015920 --- /dev/null +++ b/src/plugins/Dance/main.js @@ -0,0 +1,33 @@ +import { Vec3 } from 'vec3'; +import { readdirSync, readFileSync } from 'fs'; +import * as skills from '../../agent/library/skills.js'; +import * as world from '../../agent/library/world.js'; +import { runAsAction } from '../../agent/commands/actions.js'; +import * as mc from '../../utils/mcdata.js'; + +export class PluginInstance { + constructor(agent) { + this.agent = agent; + } + + init() { + } + + getPluginActions() { + return [ + { + name: '!dancePoping', + description: 'Dance poping.', + params: { + 'duration': {type: 'int', description: 'The time duration (in millions seconds, i.e. 1000 for 1 second) of dancing.'}, + }, + perform: runAsAction(async (agent, duration) => { + this.agent.bot.chat("I am dancing~"); + this.agent.bot.setControlState("jump", true); + await new Promise((resolve) => setTimeout(resolve, duration)); + this.agent.bot.setControlState("jump", false); + }) + }, + ] + } +} \ No newline at end of file