From c7b115ebbfa9405577cc9a4cfd387f3ed50fe8cb Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sat, 4 May 2024 15:42:30 -0500 Subject: [PATCH] refactored to SelfPrompter class and interruption logic --- src/agent/agent.js | 106 ++++++++++------------------------ src/agent/commands/actions.js | 15 ++++- src/agent/modes.js | 13 +---- src/agent/self_prompter.js | 84 +++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 87 deletions(-) create mode 100644 src/agent/self_prompter.js diff --git a/src/agent/agent.js b/src/agent/agent.js index 0aefb23..1f782de 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -6,6 +6,7 @@ import { initBot } from '../utils/mcdata.js'; import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js'; import { NPCContoller } from './npc/controller.js'; import { MemoryBank } from './memory_bank.js'; +import { SelfPrompter } from './self_prompter.js'; export class Agent { @@ -16,6 +17,7 @@ export class Agent { this.coder = new Coder(this); this.npc = new NPCContoller(this); this.memory_bank = new MemoryBank(); + this.self_prompter = new SelfPrompter(this); await this.prompter.initExamples(); @@ -27,10 +29,6 @@ export class Agent { if (load_mem) this.history.load(); - this.auto_prompting = false; - this.last_user_prompted_action = 0; - this.interrupt_auto_prompt = false; - this.bot.once('spawn', async () => { // wait for a bit so stats are not undefined await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -103,19 +101,14 @@ export class Agent { } if (execute_res) this.cleanChat(execute_res); - if (isAction(user_command_name)) { - this.last_user_prompted_action = Date.now(); - if (this.auto_prompting) { - this.interrupt_auto_prompt = true; - this.bot.chat('User initiated action. Stopping auto-prompting.'); - } - } return true; } } - const MAX_ATTEMPTS = 5; + let MAX_ATTEMPTS = 5; + if (!self_prompt && this.self_prompter.on) + MAX_ATTEMPTS = 1; // immediately respond to this message, then let self-prompting take over for (let i=0; i 0) - chat_message = `${pre_message} ${chat_message}`; - this.cleanChat(chat_message); - - if (!self_prompt && isAction(command_name)) { - console.log('User initiated action.'); - this.last_user_prompted_action = Date.now(); - if (this.auto_prompting) { - this.interrupt_auto_prompt = true; - this.bot.chat('User initiated action. Stopping auto-prompting.'); - } + if (command_name === '!stopSelfPrompt' && self_prompt) { + this.history.add('system', `Cannot stopSelfPrompt unless requested by user.`); + continue; + } + + + // let pre_message = res.substring(0, res.indexOf(command_name)).trim(); + // let chat_message = `*used ${command_name.substring(1)}*`; + // if (pre_message.length > 0) + // chat_message = `${pre_message} ${chat_message}`; + this.cleanChat(res); + + if (self_prompt && this.self_prompter.on && this.self_prompter.interrupt) break; + + if (isAction(command_name) && !self_prompt && this.self_prompter.on) { + this.self_prompter.stopLoop(); // so agent doesn't respond from self-prompting loop + // will be automatically restarted by self-prompter } - if (self_prompt && this.interrupt_auto_prompt) break; let execute_res = await executeCommand(this, res); @@ -169,51 +165,6 @@ export class Agent { return used_command; } - async autoPrompt(prompt) { - if (this.auto_prompting) { - return 'Agent is already auto-prompting. Ignoring request.'; - } - this.auto_prompting = true; - let first = true; - let no_command_count = 0; - const MAX_NO_COMMAND = 3; - while (!this.interrupt_auto_prompt) { - let msg; - if (first) { - msg = prompt; - first = false; - } - else { - msg = `You are self-prompting with the intial message: '${prompt}'. Your next response MUST contain a command !likeThis. Respond:`; - } - - let used_command = await this.handleMessage('system', msg, true); - - if (!used_command) { - no_command_count++; - if (no_command_count >= MAX_NO_COMMAND) { - let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`; - this.bot.chat(out); - console.warn(out); - break; - } - } - else - no_command_count = 0; - } - this.auto_prompting = false; - this.interrupt_auto_prompt = false; - return 'Auto-prompting finished.'; - } - - async waitStopAutoPrompt() { - while (this.auto_prompting) { - this.interrupt_auto_prompt = true; - await new Promise((resolve) => setTimeout(resolve, 500)); - } - this.interrupt_auto_prompt = false; - } - startEvents() { // Custom events this.bot.on('time', () => { @@ -270,20 +221,27 @@ export class Agent { // This update loop ensures that each update() is called one at a time, even if it takes longer than the interval const INTERVAL = 300; + let last = Date.now(); setTimeout(async () => { while (true) { let start = Date.now(); - await this.bot.modes.update(); + await this.update(start - last); let remaining = INTERVAL - (Date.now() - start); if (remaining > 0) { await new Promise((resolve) => setTimeout(resolve, remaining)); } + last = start; } }, INTERVAL); this.bot.emit('idle'); } + async update(delta) { + await this.bot.modes.update(); + await this.self_prompter.update(delta); + } + isIdle() { return !this.coder.executing && !this.coder.generating; } diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 97814c6..b8fbe38 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -37,7 +37,10 @@ export const actionsList = [ agent.coder.clear(); agent.coder.cancelResume(); agent.bot.emit('idle'); - return 'Agent stopped.'; + let msg = 'Agent stopped.'; + if (agent.self_prompter.on) + msg += ' Self-prompting still active.'; + return msg; } }, { @@ -241,7 +244,15 @@ export const actionsList = [ 'prompt': '(string) The starting prompt.', }, perform: async function (agent, prompt) { - agent.autoPrompt(prompt); // don't await, don't return + agent.self_prompter.start(prompt); // don't await, don't return } }, + { + name: '!stopSelfPrompt', + description: 'Stop current action and self-prompting.', + perform: async function (agent) { + agent.self_prompter.stop(); // will stop and + return 'Self-prompting stopped.'; + } + } ]; diff --git a/src/agent/modes.js b/src/agent/modes.js index 6b41840..f2e86e4 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -202,22 +202,13 @@ const modes = [ ]; async function execute(mode, agent, func, timeout=-1) { - let was_auto_prompting = agent.auto_prompting; - if (was_auto_prompting) agent.interrupt_auto_prompt = true; + if (agent.self_prompter.on) + agent.self_prompter.stopLoop(); mode.active = true; let code_return = await agent.coder.execute(async () => { await func(); }, timeout); mode.active = false; - if (was_auto_prompting) { - setTimeout(() => { // don't await - if (agent.isIdle()) { // not gonna work if another action is triggered because auto_prompting will be false. need to fix - let output = agent.bot.output; - let reprompt = `Agent was interrupted by ${mode.name} mode. Code output: "${output}". Continue self prompting:`; - agent.autoPrompt(reprompt); - } - }, 1000); // wait 1 second before re-enabling auto_prompt bc often another action will be triggered - } console.log(`Mode ${mode.name} finished executing, code_return: ${code_return.message}`); } diff --git a/src/agent/self_prompter.js b/src/agent/self_prompter.js new file mode 100644 index 0000000..c3daaa3 --- /dev/null +++ b/src/agent/self_prompter.js @@ -0,0 +1,84 @@ +export class SelfPrompter { + constructor(agent) { + this.agent = agent; + this.on = false; + this.loop_active = false; + this.interrupt = false; + this.prompt = ''; + this.idle_time = 0; + this.restart_after = 1000; + } + + start(prompt) { + console.log('Self-prompting started.'); + if (this.on) { + return 'Agent is already self-prompting. Ignoring request.'; + } + this.on = true; + this.prompt = prompt; + this.startLoop(); + } + + async startLoop() { + if (this.loop_active) { + console.warn('Self-prompt loop is already active. Ignoring request.'); + return; + } + console.log('starting self-prompt loop') + this.loop_active = true; + let no_command_count = 0; + const MAX_NO_COMMAND = 3; + while (!this.interrupt) { + let msg = `You are self-prompting with the prompt: '${this.prompt}'. Your next response MUST contain a command !withThisSyntax. Respond:`; + + let used_command = await this.agent.handleMessage('system', msg, true); + if (!used_command) { + no_command_count++; + if (no_command_count >= MAX_NO_COMMAND) { + let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`; + this.agent.bot.chat(out); + console.warn(out); + this.on = false; + break; + } + } + else + no_command_count = 0; + } + console.log('self prompt loop stopped') + this.loop_active = false; + this.interrupt = false; + } + + update(delta) { + // automatically restarts loop + if (this.on && !this.loop_active && !this.interrupt) { + if (this.agent.isIdle()) + this.idle_time += delta; + else + this.idle_time = 0; + + if (this.idle_time >= this.restart_after) { + this.agent.bot.chat('Restarting self-prompting...'); + this.startLoop(); + this.idle_time = 0; + } + } + } + + async stopLoop() { + // you can call this without await if you don't need to wait for it to finish + this.interrupt = true; + while (this.loop_active) { + await new Promise(r => setTimeout(r, 500)); + } + this.interrupt = false; + } + + async stop() { + this.interrupt = true; + await this.agent.coder.stop(); + await this.stopLoop(); + this.on = false; + } +} \ No newline at end of file