add state to self prompter for pausing

This commit is contained in:
MaxRobinsonTheGreat 2025-02-20 17:17:21 -06:00
parent 7d9257036c
commit b23f4776b1
10 changed files with 68 additions and 50 deletions

View file

@ -44,6 +44,7 @@
}, },
"multiagent_techtree_1_stone_pickaxe": { "multiagent_techtree_1_stone_pickaxe": {
"conversation": "Let's collaborate to build a stone pickaxe", "conversation": "Let's collaborate to build a stone pickaxe",
"goal": "Build a stone pickaxe",
"agent_count": 2, "agent_count": 2,
"initial_inventory": { "initial_inventory": {
"0": { "0": {

View file

@ -46,7 +46,7 @@ export class ActionManager {
assert(actionLabel != null, 'actionLabel is required for new resume'); assert(actionLabel != null, 'actionLabel is required for new resume');
this.resume_name = actionLabel; this.resume_name = actionLabel;
} }
if (this.resume_func != null && (this.agent.isIdle() || new_resume) && (!this.agent.self_prompter.on || new_resume)) { if (this.resume_func != null && (this.agent.isIdle() || new_resume) && (!this.agent.self_prompter.isActive() || new_resume)) {
this.currentActionLabel = this.resume_name; this.currentActionLabel = this.resume_name;
let res = await this._executeAction(this.resume_name, this.resume_func, timeout); let res = await this._executeAction(this.resume_name, this.resume_func, timeout);
this.currentActionLabel = ''; this.currentActionLabel = '';

View file

@ -156,10 +156,10 @@ export class Agent {
}; };
if (save_data?.self_prompt) { if (save_data?.self_prompt) {
let prompt = save_data.self_prompt; if (init_message) {
// add initial message to history this.history.add('system', init_message);
this.history.add('system', prompt); }
await this.self_prompter.start(prompt); await this.self_prompter.handleLoad(save_data.self_prompt, save_data.self_prompting_state);
} }
if (save_data?.last_sender) { if (save_data?.last_sender) {
this.last_sender = save_data.last_sender; this.last_sender = save_data.last_sender;
@ -193,7 +193,7 @@ export class Agent {
shutUp() { shutUp() {
this.shut_up = true; this.shut_up = true;
if (this.self_prompter.on) { if (this.self_prompter.isActive()) {
this.self_prompter.stop(false); this.self_prompter.stop(false);
} }
convoManager.endAllConversations(); convoManager.endAllConversations();
@ -259,7 +259,7 @@ export class Agent {
await this.history.add(source, message); await this.history.add(source, message);
this.history.save(); this.history.save();
if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting if (!self_prompt && this.self_prompter.isActive()) // message is from user during self-prompting
max_responses = 1; // force only respond to this message, then let self-prompting take over max_responses = 1; // force only respond to this message, then let self-prompting take over
for (let i=0; i<max_responses; i++) { for (let i=0; i<max_responses; i++) {
if (checkInterrupt()) break; if (checkInterrupt()) break;

View file

@ -49,7 +49,7 @@ export const actionsList = [
agent.actions.cancelResume(); agent.actions.cancelResume();
agent.bot.emit('idle'); agent.bot.emit('idle');
let msg = 'Agent stopped.'; let msg = 'Agent stopped.';
if (agent.self_prompter.on) if (agent.self_prompter.isActive())
msg += ' Self-prompting still active.'; msg += ' Self-prompting still active.';
return msg; return msg;
} }
@ -362,8 +362,7 @@ export const actionsList = [
}, },
perform: async function (agent, prompt) { perform: async function (agent, prompt) {
if (convoManager.inConversation()) { if (convoManager.inConversation()) {
agent.self_prompter.setPrompt(prompt); agent.self_prompter.setPromptPaused(prompt);
convoManager.scheduleSelfPrompter();
} }
else { else {
agent.self_prompter.start(prompt); agent.self_prompter.start(prompt);
@ -375,7 +374,6 @@ export const actionsList = [
description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action. ', description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action. ',
perform: async function (agent) { perform: async function (agent) {
agent.self_prompter.stop(); agent.self_prompter.stop();
convoManager.cancelSelfPrompter();
return 'Self-prompting stopped.'; return 'Self-prompting stopped.';
} }
}, },

View file

@ -7,8 +7,6 @@ let agent;
let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
let agents_in_game = []; let agents_in_game = [];
let self_prompter_paused = false;
class Conversation { class Conversation {
constructor(name) { constructor(name) {
this.name = name; this.name = name;
@ -97,7 +95,7 @@ class ConversationManager {
this._clearMonitorTimeouts(); this._clearMonitorTimeouts();
return; return;
} }
if (!self_prompter_paused) { if (!agent.self_prompter.isPaused()) {
this.endConversation(convo_partner); this.endConversation(convo_partner);
agent.handleMessage('system', `${convo_partner} disconnected, conversation has ended.`); agent.handleMessage('system', `${convo_partner} disconnected, conversation has ended.`);
} }
@ -125,9 +123,8 @@ class ConversationManager {
const convo = this._getConvo(send_to); const convo = this._getConvo(send_to);
convo.reset(); convo.reset();
if (agent.self_prompter.on) { if (agent.self_prompter.isActive()) {
await agent.self_prompter.stop(); await agent.self_prompter.pause();
self_prompter_paused = true;
} }
if (convo.active) if (convo.active)
return; return;
@ -191,9 +188,8 @@ class ConversationManager {
convo.queue(received); convo.queue(received);
// responding to conversation takes priority over self prompting // responding to conversation takes priority over self prompting
if (agent.self_prompter.on){ if (agent.self_prompter.isActive()){
await agent.self_prompter.stopLoop(); await agent.self_prompter.pause();
self_prompter_paused = true;
} }
_scheduleProcessInMessage(sender, received, convo); _scheduleProcessInMessage(sender, received, convo);
@ -235,7 +231,7 @@ class ConversationManager {
if (this.activeConversation.name === sender) { if (this.activeConversation.name === sender) {
this._stopMonitor(); this._stopMonitor();
this.activeConversation = null; this.activeConversation = null;
if (self_prompter_paused && !this.inConversation()) { if (agent.self_prompter.isPaused() && !this.inConversation()) {
_resumeSelfPrompter(); _resumeSelfPrompter();
} }
} }
@ -246,7 +242,7 @@ class ConversationManager {
for (const sender in this.convos) { for (const sender in this.convos) {
this.endConversation(sender); this.endConversation(sender);
} }
if (self_prompter_paused) { if (agent.self_prompter.isPaused()) {
_resumeSelfPrompter(); _resumeSelfPrompter();
} }
} }
@ -258,14 +254,6 @@ class ConversationManager {
this.endConversation(sender); this.endConversation(sender);
} }
} }
scheduleSelfPrompter() {
self_prompter_paused = true;
}
cancelSelfPrompter() {
self_prompter_paused = false;
}
} }
const convoManager = new ConversationManager(); const convoManager = new ConversationManager();
@ -360,8 +348,7 @@ function _tagMessage(message) {
async function _resumeSelfPrompter() { async function _resumeSelfPrompter() {
await new Promise(resolve => setTimeout(resolve, 5000)); await new Promise(resolve => setTimeout(resolve, 5000));
if (self_prompter_paused && !convoManager.inConversation()) { if (agent.self_prompter.isPaused() && !convoManager.inConversation()) {
self_prompter_paused = false;
agent.self_prompter.start(); agent.self_prompter.start();
} }
} }

View file

@ -84,7 +84,8 @@ export class History {
const data = { const data = {
memory: this.memory, memory: this.memory,
turns: this.turns, turns: this.turns,
self_prompt: this.agent.self_prompter.on ? this.agent.self_prompter.prompt : null, self_prompting_state: this.agent.self_prompter.state,
self_prompt: this.agent.self_prompter.isStopped() ? null : this.agent.self_prompter.prompt,
last_sender: this.agent.last_sender last_sender: this.agent.last_sender
}; };
writeFileSync(this.memory_fp, JSON.stringify(data, null, 2)); writeFileSync(this.memory_fp, JSON.stringify(data, null, 2));

View file

@ -277,7 +277,7 @@ const modes_list = [
]; ];
async function execute(mode, agent, func, timeout=-1) { async function execute(mode, agent, func, timeout=-1) {
if (agent.self_prompter.on) if (agent.self_prompter.isActive())
agent.self_prompter.stopLoop(); agent.self_prompter.stopLoop();
let interrupted_action = agent.actions.currentActionLabel; let interrupted_action = agent.actions.currentActionLabel;
mode.active = true; mode.active = true;
@ -290,7 +290,7 @@ async function execute(mode, agent, func, timeout=-1) {
let should_reprompt = let should_reprompt =
interrupted_action && // it interrupted a previous action interrupted_action && // it interrupted a previous action
!agent.actions.resume_func && // there is no resume function !agent.actions.resume_func && // there is no resume function
!agent.self_prompter.on && // self prompting is not on !agent.self_prompter.isActive() && // self prompting is not on
!code_return.interrupted; // this mode action was not interrupted by something else !code_return.interrupted; // this mode action was not interrupted by something else
if (should_reprompt) { if (should_reprompt) {
@ -311,9 +311,9 @@ for (let mode of modes_list) {
class ModeController { class ModeController {
/* /*
SECURITY WARNING: SECURITY WARNING:
ModesController must be isolated. Do not store references to external objects like `agent`. ModesController must be reference isolated. Do not store references to external objects like `agent`.
This object is accessible by LLM generated code, so any stored references are also accessible. This object is accessible by LLM generated code, so any stored references are also accessible.
This can be used to expose sensitive information by malicious human prompters. This can be used to expose sensitive information by malicious prompters.
*/ */
constructor() { constructor() {
this.behavior_log = ''; this.behavior_log = '';

View file

@ -1,7 +1,10 @@
const STOPPED = 0
const ACTIVE = 1
const PAUSED = 2
export class SelfPrompter { export class SelfPrompter {
constructor(agent) { constructor(agent) {
this.agent = agent; this.agent = agent;
this.on = false; this.state = STOPPED;
this.loop_active = false; this.loop_active = false;
this.interrupt = false; this.interrupt = false;
this.prompt = ''; this.prompt = '';
@ -16,16 +19,38 @@ export class SelfPrompter {
return 'No prompt specified. Ignoring request.'; return 'No prompt specified. Ignoring request.';
prompt = this.prompt; prompt = this.prompt;
} }
if (this.on) { this.state = ACTIVE;
this.prompt = prompt;
}
this.on = true;
this.prompt = prompt; this.prompt = prompt;
this.startLoop(); this.startLoop();
} }
setPrompt(prompt) { isActive() {
return this.state === ACTIVE;
}
isStopped() {
return this.state === STOPPED;
}
isPaused() {
return this.state === PAUSED;
}
async handleLoad(prompt, state) {
if (state == undefined)
state = STOPPED;
this.state = state;
this.prompt = prompt; this.prompt = prompt;
if (state !== STOPPED && !prompt)
throw new Error('No prompt loaded when self-prompting is active');
if (state === ACTIVE) {
await this.start(prompt);
}
}
setPromptPaused(prompt) {
this.prompt = prompt;
this.state = PAUSED;
} }
async startLoop() { async startLoop() {
@ -47,7 +72,7 @@ export class SelfPrompter {
let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`; let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`;
this.agent.openChat(out); this.agent.openChat(out);
console.warn(out); console.warn(out);
this.on = false; this.state = STOPPED;
break; break;
} }
} }
@ -63,7 +88,7 @@ export class SelfPrompter {
update(delta) { update(delta) {
// automatically restarts loop // automatically restarts loop
if (this.on && !this.loop_active && !this.interrupt) { if (this.state === ACTIVE && !this.loop_active && !this.interrupt) {
if (this.agent.isIdle()) if (this.agent.isIdle())
this.idle_time += delta; this.idle_time += delta;
else else
@ -96,12 +121,17 @@ export class SelfPrompter {
this.interrupt = true; this.interrupt = true;
if (stop_action) if (stop_action)
await this.agent.actions.stop(); await this.agent.actions.stop();
await this.stopLoop(); this.stopLoop();
this.on = false; this.state = STOPPED;
}
async pause() {
this.interrupt = true;
this.state = PAUSED;
} }
shouldInterrupt(is_self_prompt) { // to be called from handleMessage shouldInterrupt(is_self_prompt) { // to be called from handleMessage
return is_self_prompt && this.on && this.interrupt; return is_self_prompt && this.state === ACTIVE && this.interrupt;
} }
handleUserPromptedCmd(is_self_prompt, is_action) { handleUserPromptedCmd(is_self_prompt, is_action) {

View file

@ -82,7 +82,7 @@ export class Task {
if (this.validator && this.validator.validate()) if (this.validator && this.validator.validate())
return {"message": 'Task successful', "code": 2}; return {"message": 'Task successful', "code": 2};
// TODO check for other terminal conditions // TODO check for other terminal conditions
// if (this.task.goal && !this.self_prompter.on) // if (this.task.goal && !this.self_prompter.isActive())
// return {"message": 'Agent ended goal', "code": 3}; // return {"message": 'Agent ended goal', "code": 3};
// if (this.task.conversation && !inConversation()) // if (this.task.conversation && !inConversation())
// return {"message": 'Agent ended conversation', "code": 3}; // return {"message": 'Agent ended conversation', "code": 3};

View file

@ -257,7 +257,8 @@ export class Prompter {
if (prompt.includes('$CONVO')) if (prompt.includes('$CONVO'))
prompt = prompt.replaceAll('$CONVO', 'Recent conversation:\n' + stringifyTurns(messages)); prompt = prompt.replaceAll('$CONVO', 'Recent conversation:\n' + stringifyTurns(messages));
if (prompt.includes('$SELF_PROMPT')) { if (prompt.includes('$SELF_PROMPT')) {
let self_prompt = this.agent.self_prompter.on ? `YOUR CURRENT ASSIGNED GOAL: "${this.agent.self_prompter.prompt}"\n` : ''; // if active or paused, show the current goal
let self_prompt = !this.agent.self_prompter.isStopped() ? `YOUR CURRENT ASSIGNED GOAL: "${this.agent.self_prompter.prompt}"\n` : '';
prompt = prompt.replaceAll('$SELF_PROMPT', self_prompt); prompt = prompt.replaceAll('$SELF_PROMPT', self_prompt);
} }
if (prompt.includes('$LAST_GOALS')) { if (prompt.includes('$LAST_GOALS')) {