refactor: actions

This commit is contained in:
RainbowBird
2025-01-07 01:58:36 +08:00
parent 4fc1c4e44a
commit 88af318f0a
8 changed files with 517 additions and 572 deletions
+317 -435
View File
@@ -2,136 +2,113 @@ import type { BotContext } from 'src/composables/bot'
import { z } from 'zod'
import * as skills from '../skills'
function runAsAction(actionFn, resume = false, timeout = -1) {
let actionLabel = null // Will be set on first use
const wrappedAction = async function (agent, ...args) {
// Set actionLabel only once, when the action is first created
if (!actionLabel) {
const actionObj = actionsList.find(a => a.perform === wrappedAction)
actionLabel = actionObj.name.substring(1) // Remove the ! prefix
}
const actionFnWithAgent = async () => {
await actionFn(agent, ...args)
}
const code_return = await agent.actions.runAction(`action:${actionLabel}`, actionFnWithAgent, { timeout, resume })
if (code_return.interrupted && !code_return.timedout)
return
return code_return.message
}
return wrappedAction
}
type ActionResult = string | Promise<string>
interface Action {
readonly name: string
readonly description: string
readonly schema: z.ZodObject<any>
readonly perform: (ctx: BotContext) => () => ActionResult
readonly perform: (ctx: BotContext) => (...args: any[]) => ActionResult
}
function getNewAction(): Action {
return {
name: '!newAction',
description: 'Perform new and unknown custom behaviors that are not available as a command.',
schema: z.object({
prompt: z.string().describe('A natural language prompt to guide code generation. Make a detailed step-by-step plan.'),
}),
perform: (agent: BotContext) => async () => {
if (!settings.allow_insecure_coding)
return 'newAction not allowed! Code writing is disabled in settings. Notify the user.'
return await agent.coder.generateCode(agent.history)
},
}
}
export const actionsList: Action[] = [
// getNewAction(): Action {
// return {
// name: 'newAction',
// description: 'Perform new and unknown custom behaviors that are not available as a command.',
// schema: z.object({
// prompt: z.string().describe('A natural language prompt to guide code generation. Make a detailed step-by-step plan.'),
// }),
// perform: (ctx: BotContext) => async (prompt: string) => {
// if (!settings.allow_insecure_coding)
// return 'newAction not allowed! Code writing is disabled in settings. Notify the user.'
// return await ctx.coder.generateCode(ctx.history)
// },
// }
// },
function getStopAction(): Action {
return {
name: '!stop',
description: 'Force stop all actions and commands that are currently executing.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
await agent.actions.stop()
agent.clearBotLogs()
agent.actions.cancelResume()
agent.bot.emit('idle')
let msg = 'Agent stopped.'
if (agent.self_prompter.on)
msg += ' Self-prompting still active.'
return msg
},
}
}
// getStopAction(): Action {
// return {
// name: 'stop',
// description: 'Force stop all actions and commands that are currently executing.',
// schema: z.object({}),
// perform: (ctx: BotContext) => async () => {
// await ctx.actions.stop()
// ctx.clearBotLogs()
// ctx.actions.cancelResume()
// ctx.bot.emit('idle')
// let msg = 'Agent stopped.'
// if (ctx.self_prompter.on)
// msg += ' Self-prompting still active.'
// return msg
// },
// }
// },
function getStfuAction(): Action {
return {
name: '!stfu',
description: 'Stop all chatting and self prompting, but continue current action.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.openChat('Shutting up.')
agent.shutUp()
},
}
}
// getStfuAction(): Action {
// return {
// name: 'stfu',
// description: 'Stop all chatting and self prompting, but continue current action.',
// schema: z.object({}),
// perform: (ctx: BotContext) => async () => {
// ctx.openChat('Shutting up.')
// ctx.shutUp()
// return 'Shutting up.'
// },
// }
// },
function getRestartAction(): Action {
return {
name: '!restart',
description: 'Restart the agent process.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.cleanKill()
},
}
}
// getRestartAction(): Action {
// return {
// name: 'restart',
// description: 'Restart the agent process.',
// schema: z.object({}),
// perform: (ctx: BotContext) => async () => {
// ctx.cleanKill()
// return 'Restarting agent...'
// },
// }
// },
function getClearChatAction(): Action {
return {
name: '!clearChat',
description: 'Clear the chat history.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.history.clear()
return `${agent.name}'s chat history was cleared, starting new conversation from scratch.`
},
}
}
function getGoToPlayerAction(): Action {
return {
name: '!goToPlayer',
// getClearChatAction(): Action {
// return {
// name: 'clearChat',
// description: 'Clear the chat history.',
// schema: z.object({}),
// perform: (ctx: BotContext) => async () => {
// ctx.history.clear()
// return `${ctx.name}'s chat history was cleared, starting new conversation from scratch.`
// },
// }
// },
{
name: 'goToPlayer',
description: 'Go to the given player.',
schema: z.object({
player_name: z.string().describe('The name of the player to go to.'),
closeness: z.number().describe('How close to get to the player.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name, closeness) => {
return await skills.goToPlayer(agent.bot, player_name, closeness)
}),
}
}
perform: (ctx: BotContext) => async (player_name: string, closeness: number) => {
await skills.goToPlayer(ctx.bot, player_name, closeness)
return 'Moving to player...'
},
},
function getFollowPlayerAction(): Action {
return {
name: '!followPlayer',
{
name: 'followPlayer',
description: 'Endlessly follow the given player.',
schema: z.object({
player_name: z.string().describe('name of the player to follow.'),
follow_dist: z.number().describe('The distance to follow from.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name, follow_dist) => {
await skills.followPlayer(agent.bot, player_name, follow_dist)
}, true),
}
}
perform: (ctx: BotContext) => async (player_name: string, follow_dist: number) => {
await skills.followPlayer(ctx.bot, player_name, follow_dist)
return 'Following player...'
},
},
function getGoToCoordinatesAction(): Action {
return {
name: '!goToCoordinates',
{
name: 'goToCoordinates',
description: 'Go to the given x, y, z location.',
schema: z.object({
x: z.number().describe('The x coordinate.'),
@@ -139,445 +116,350 @@ function getGoToCoordinatesAction(): Action {
z: z.number().describe('The z coordinate.'),
closeness: z.number().describe('How close to get to the location.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, x, y, z, closeness) => {
await skills.goToPosition(agent.bot, x, y, z, closeness)
}),
}
}
perform: (ctx: BotContext) => async (x: number, y: number, z: number, closeness: number) => {
await skills.goToPosition(ctx.bot, x, y, z, closeness)
return 'Moving to coordinates...'
},
},
function getSearchForBlockAction(): Action {
return {
name: '!searchForBlock',
{
name: 'searchForBlock',
description: 'Find and go to the nearest block of a given type in a given range.',
schema: z.object({
type: z.string().describe('The block type to go to.'),
search_range: z.number().describe('The range to search for the block.').min(32).max(512),
}),
perform: (agent: BotContext) => runAsAction(async (agent, block_type, range) => {
await skills.goToNearestBlock(agent.bot, block_type, 4, range)
}),
}
}
perform: (ctx: BotContext) => async (block_type: string, range: number) => {
await skills.goToNearestBlock(ctx.bot, block_type, 4, range)
return 'Searching for block...'
},
},
function getSearchForEntityAction(): Action {
return {
name: '!searchForEntity',
{
name: 'searchForEntity',
description: 'Find and go to the nearest entity of a given type in a given range.',
schema: z.object({
type: z.string().describe('The type of entity to go to.'),
search_range: z.number().describe('The range to search for the entity.').min(32).max(512),
}),
perform: (agent: BotContext) => runAsAction(async (agent, entity_type, range) => {
await skills.goToNearestEntity(agent.bot, entity_type, 4, range)
}),
}
}
perform: (ctx: BotContext) => async (entity_type: string, range: number) => {
await skills.goToNearestEntity(ctx.bot, entity_type, 4, range)
return 'Searching for entity...'
},
},
function getMoveAwayAction(): Action {
return {
name: '!moveAway',
{
name: 'moveAway',
description: 'Move away from the current location in any direction by a given distance.',
schema: z.object({
distance: z.number().describe('The distance to move away.').min(0),
}),
perform: (agent: BotContext) => runAsAction(async (agent, distance) => {
await skills.moveAway(agent.bot, distance)
}),
}
}
function getRememberHereAction(): Action {
return {
name: '!rememberHere',
description: 'Save the current location with a given name.',
schema: z.object({
name: z.string().describe('The name to remember the location as.'),
}),
perform: (agent: BotContext) => async (name: string) => {
const pos = agent.bot.entity.position
agent.memory_bank.rememberPlace(name, pos.x, pos.y, pos.z)
return `Location saved as "${name}".`
perform: (ctx: BotContext) => async (distance: number) => {
await skills.moveAway(ctx.bot, distance)
return 'Moving away...'
},
}
}
},
function getGoToRememberedPlaceAction(): Action {
return {
name: '!goToRememberedPlace',
description: 'Go to a saved location.',
schema: z.object({
name: z.string().describe('The name of the location to go to.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, name) => {
const pos = agent.memory_bank.recallPlace(name)
if (!pos) {
skills.log(agent.bot, `No location named "${name}" saved.`)
return
}
await skills.goToPosition(agent.bot, pos[0], pos[1], pos[2], 1)
}),
}
}
function getGivePlayerAction(): Action {
return {
name: '!givePlayer',
{
name: 'givePlayer',
description: 'Give the specified item to the given player.',
schema: z.object({
player_name: z.string().describe('The name of the player to give the item to.'),
item_name: z.string().describe('The name of the item to give.'),
num: z.number().int().describe('The number of items to give.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name, item_name, num) => {
await skills.giveToPlayer(agent.bot, item_name, player_name, num)
}),
}
}
perform: (ctx: BotContext) => async (player_name: string, item_name: string, num: number) => {
await skills.giveToPlayer(ctx.bot, item_name, player_name, num)
return 'Giving items to player...'
},
},
function getConsumeAction(): Action {
return {
name: '!consume',
{
name: 'consume',
description: 'Eat/drink the given item.',
schema: z.object({
item_name: z.string().describe('The name of the item to consume.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name) => {
await skills.consume(agent.bot, item_name)
}),
}
}
perform: (ctx: BotContext) => async (item_name: string) => {
await skills.consume(ctx.bot, item_name)
return 'Consuming item...'
},
},
function getEquipAction(): Action {
return {
name: '!equip',
{
name: 'equip',
description: 'Equip the given item.',
schema: z.object({
item_name: z.string().describe('The name of the item to equip.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name) => {
await skills.equip(agent.bot, item_name)
}),
}
}
perform: (ctx: BotContext) => async (item_name: string) => {
await skills.equip(ctx.bot, item_name)
return 'Equipping item...'
},
},
function getPutInChestAction(): Action {
return {
name: '!putInChest',
{
name: 'putInChest',
description: 'Put the given item in the nearest chest.',
schema: z.object({
item_name: z.string().describe('The name of the item to put in the chest.'),
num: z.number().int().describe('The number of items to put in the chest.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
await skills.putInChest(agent.bot, item_name, num)
}),
}
}
perform: (ctx: BotContext) => async (item_name: string, num: number) => {
await skills.putInChest(ctx.bot, item_name, num)
return 'Putting items in chest...'
},
},
function getTakeFromChestAction(): Action {
return {
name: '!takeFromChest',
{
name: 'takeFromChest',
description: 'Take the given items from the nearest chest.',
schema: z.object({
item_name: z.string().describe('The name of the item to take.'),
num: z.number().int().describe('The number of items to take.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
await skills.takeFromChest(agent.bot, item_name, num)
}),
}
}
perform: (ctx: BotContext) => async (item_name: string, num: number) => {
await skills.takeFromChest(ctx.bot, item_name, num)
return 'Taking items from chest...'
},
},
function getViewChestAction(): Action {
return {
name: '!viewChest',
{
name: 'viewChest',
description: 'View the items/counts of the nearest chest.',
schema: z.object({}),
perform: (agent: BotContext) => runAsAction(async (agent) => {
await skills.viewChest(agent.bot)
}),
}
}
perform: (ctx: BotContext) => async () => {
await skills.viewChest(ctx.bot)
return 'Viewing chest contents...'
},
},
function getDiscardAction(): Action {
return {
name: '!discard',
{
name: 'discard',
description: 'Discard the given item from the inventory.',
schema: z.object({
item_name: z.string().describe('The name of the item to discard.'),
num: z.number().int().describe('The number of items to discard.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
const start_loc = agent.bot.entity.position
await skills.moveAway(agent.bot, 5)
await skills.discard(agent.bot, item_name, num)
await skills.goToPosition(agent.bot, start_loc.x, start_loc.y, start_loc.z, 0)
}),
}
}
perform: (ctx: BotContext) => async (item_name: string, num: number) => {
const start_loc = ctx.bot.entity.position
await skills.moveAway(ctx.bot, 5)
await skills.discard(ctx.bot, item_name, num)
await skills.goToPosition(ctx.bot, start_loc.x, start_loc.y, start_loc.z, 0)
return 'Discarding items...'
},
},
function getCollectBlocksAction(): Action {
return {
name: '!collectBlocks',
{
name: 'collectBlocks',
description: 'Collect the nearest blocks of a given type.',
schema: z.object({
type: z.string().describe('The block type to collect.'),
num: z.number().int().describe('The number of blocks to collect.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type, num) => {
await skills.collectBlock(agent.bot, type, num)
}, false, 10), // 10 minute timeout
}
}
perform: (ctx: BotContext) => async (type: string, num: number) => {
await skills.collectBlock(ctx.bot, type, num)
return 'Collecting blocks...'
},
},
function getCraftRecipeAction(): Action {
return {
name: '!craftRecipe',
{
name: 'craftRecipe',
description: 'Craft the given recipe a given number of times.',
schema: z.object({
recipe_name: z.string().describe('The name of the output item to craft.'),
num: z.number().int().describe('The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, recipe_name, num) => {
await skills.craftRecipe(agent.bot, recipe_name, num)
}),
}
}
perform: (ctx: BotContext) => async (recipe_name: string, num: number) => {
await skills.craftRecipe(ctx.bot, recipe_name, num)
return 'Crafting items...'
},
},
function getSmeltItemAction(): Action {
return {
name: '!smeltItem',
{
name: 'smeltItem',
description: 'Smelt the given item the given number of times.',
schema: z.object({
item_name: z.string().describe('The name of the input item to smelt.'),
num: z.number().int().describe('The number of times to smelt the item.').min(1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, item_name, num) => {
const success = await skills.smeltItem(agent.bot, item_name, num)
if (success) {
setTimeout(() => {
agent.cleanKill('Safely restarting to update inventory.')
}, 500)
}
}),
}
}
perform: (ctx: BotContext) => async (item_name: string, num: number) => {
await skills.smeltItem(ctx.bot, item_name, num)
return 'Smelting items...'
},
},
function getClearFurnaceAction(): Action {
return {
name: '!clearFurnace',
{
name: 'clearFurnace',
description: 'Take all items out of the nearest furnace.',
schema: z.object({}),
perform: (agent: BotContext) => runAsAction(async (agent) => {
await skills.clearNearestFurnace(agent.bot)
}),
}
}
perform: (ctx: BotContext) => async () => {
await skills.clearNearestFurnace(ctx.bot)
return 'Clearing furnace...'
},
},
function getPlaceHereAction(): Action {
return {
name: '!placeHere',
{
name: 'placeHere',
description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.',
schema: z.object({
type: z.string().describe('The block type to place.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type) => {
const pos = agent.bot.entity.position
await skills.placeBlock(agent.bot, type, pos.x, pos.y, pos.z)
}),
}
}
perform: (ctx: BotContext) => async (type: string) => {
const pos = ctx.bot.entity.position
await skills.placeBlock(ctx.bot, type, pos.x, pos.y, pos.z)
return 'Placing block...'
},
},
function getAttackAction(): Action {
return {
name: '!attack',
{
name: 'attack',
description: 'Attack and kill the nearest entity of a given type.',
schema: z.object({
type: z.string().describe('The type of entity to attack.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type) => {
await skills.attackNearest(agent.bot, type, true)
}),
}
}
perform: (ctx: BotContext) => async (type: string) => {
await skills.attackNearest(ctx.bot, type, true)
return 'Attacking entity...'
},
},
function getAttackPlayerAction(): Action {
return {
name: '!attackPlayer',
{
name: 'attackPlayer',
description: 'Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm.',
schema: z.object({
player_name: z.string().describe('The name of the player to attack.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, player_name) => {
const player = agent.bot.players[player_name]?.entity
perform: (ctx: BotContext) => async (player_name: string) => {
const player = ctx.bot.players[player_name]?.entity
if (!player) {
skills.log(agent.bot, `Could not find player ${player_name}.`)
return false
skills.log(ctx.bot, `Could not find player ${player_name}.`)
return 'Player not found'
}
await skills.attackEntity(agent.bot, player, true)
}),
}
}
await skills.attackEntity(ctx.bot, player, true)
return 'Attacking player...'
},
},
function getGoToBedAction(): Action {
return {
name: '!goToBed',
{
name: 'goToBed',
description: 'Go to the nearest bed and sleep.',
schema: z.object({}),
perform: (agent: BotContext) => runAsAction(async (agent) => {
await skills.goToBed(agent.bot)
}),
}
}
perform: (ctx: BotContext) => async () => {
await skills.goToBed(ctx.bot)
return 'Going to bed...'
},
},
function getActivateAction(): Action {
return {
name: '!activate',
{
name: 'activate',
description: 'Activate the nearest object of a given type.',
schema: z.object({
type: z.string().describe('The type of object to activate.'),
}),
perform: (agent: BotContext) => runAsAction(async (agent, type) => {
await skills.activateNearestBlock(agent.bot, type)
}),
}
}
perform: (ctx: BotContext) => async (type: string) => {
await skills.activateNearestBlock(ctx.bot, type)
return 'Activating block...'
},
},
function getStayAction(): Action {
return {
name: '!stay',
{
name: 'stay',
description: 'Stay in the current location no matter what. Pauses all modes.',
schema: z.object({
type: z.number().int().describe('The number of seconds to stay. -1 for forever.').min(-1),
}),
perform: (agent: BotContext) => runAsAction(async (agent, seconds) => {
await skills.stay(agent.bot, seconds)
}),
}
}
function getSetModeAction(): Action {
return {
name: '!setMode',
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.',
schema: z.object({
mode_name: z.string().describe('The name of the mode to enable.'),
on: z.boolean().describe('Whether to enable or disable the mode.'),
}),
perform: (agent: BotContext) => async (mode_name: string, on: boolean) => {
const modes = agent.bot.modes
if (!modes.exists(mode_name))
return `Mode ${mode_name} does not exist.${modes.getDocs()}`
if (modes.isOn(mode_name) === on)
return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.`
modes.setOn(mode_name, on)
return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.`
perform: (ctx: BotContext) => async (seconds: number) => {
await skills.stay(ctx.bot, seconds)
return 'Staying in place...'
},
}
}
},
// getSetModeAction(): Action {
// return {
// name: 'setMode',
// description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.',
// schema: z.object({
// mode_name: z.string().describe('The name of the mode to enable.'),
// on: z.boolean().describe('Whether to enable or disable the mode.'),
// }),
// perform: (ctx: BotContext) => async (mode_name: string, on: boolean) => {
// const modes = ctx.bot.modes
// if (!modes.exists(mode_name))
// return `Mode ${mode_name} does not exist.${modes.getDocs()}`
// if (modes.isOn(mode_name) === on)
// return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.`
// modes.setOn(mode_name, on)
// return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.`
// },
// }
// },
function getGoalAction(): Action {
return {
name: '!goal',
description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.',
schema: z.object({
selfPrompt: z.string().describe('The goal prompt.'),
}),
perform: (agent: BotContext) => async (prompt: string) => {
if (convoManager.inConversation()) {
agent.self_prompter.setPrompt(prompt)
convoManager.scheduleSelfPrompter()
}
else {
agent.self_prompter.start(prompt)
}
},
}
}
// getGoalAction(): Action {
// return {
// name: 'goal',
// description: 'Set a goal prompt to endlessly work towards with continuous self-prompting.',
// schema: z.object({
// selfPrompt: z.string().describe('The goal prompt.'),
// }),
// perform: (ctx: BotContext) => async (prompt: string) => {
// if (convoManager.inConversation()) {
// ctx.self_prompter.setPrompt(prompt)
// convoManager.scheduleSelfPrompter()
// }
// else {
// ctx.self_prompter.start(prompt)
// }
// return 'Goal set...'
// },
// }
// },
function getEndGoalAction(): Action {
return {
name: '!endGoal',
description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action.',
schema: z.object({}),
perform: (agent: BotContext) => async () => {
agent.self_prompter.stop()
convoManager.cancelSelfPrompter()
return 'Self-prompting stopped.'
},
}
}
// getEndGoalAction(): Action {
// return {
// name: 'endGoal',
// description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action.',
// schema: z.object({}),
// perform: (ctx: BotContext) => async () => {
// ctx.self_prompter.stop()
// convoManager.cancelSelfPrompter()
// return 'Self-prompting stopped.'
// },
// }
// },
function getStartConversationAction(): Action {
return {
name: '!startConversation',
description: 'Start a conversation with a player. Use for bots only.',
schema: z.object({
player_name: z.string().describe('The name of the player to send the message to.'),
message: z.string().describe('The message to send.'),
}),
perform: (agent: BotContext) => async (player_name: string, message: string) => {
if (!convoManager.isOtherAgent(player_name))
return `${player_name} is not a bot, cannot start conversation.`
if (convoManager.inConversation() && !convoManager.inConversation(player_name))
convoManager.forceEndCurrentConversation()
else if (convoManager.inConversation(player_name))
agent.history.add('system', `You are already in conversation with ${player_name}. Don't use this command to talk to them.`)
convoManager.startConversation(player_name, message)
},
}
}
// getStartConversationAction(): Action {
// return {
// name: 'startConversation',
// description: 'Start a conversation with a player. Use for bots only.',
// schema: z.object({
// player_name: z.string().describe('The name of the player to send the message to.'),
// message: z.string().describe('The message to send.'),
// }),
// perform: (ctx: BotContext) => async (player_name: string, message: string) => {
// if (!convoManager.isOtherAgent(player_name))
// return `${player_name} is not a bot, cannot start conversation.`
// if (convoManager.inConversation() && !convoManager.inConversation(player_name))
// convoManager.forceEndCurrentConversation()
// else if (convoManager.inConversation(player_name))
// ctx.history.add('system', `You are already in conversation with ${player_name}. Don't use this command to talk to them.`)
// convoManager.startConversation(player_name, message)
// },
// }
// },
function getEndConversationAction(): Action {
return {
name: '!endConversation',
description: 'End the conversation with the given player.',
schema: z.object({
player_name: z.string().describe('The name of the player to end the conversation with.'),
}),
perform: (agent: BotContext) => async (player_name: string) => {
if (!convoManager.inConversation(player_name))
return `Not in conversation with ${player_name}.`
convoManager.endConversation(player_name)
return `Converstaion with ${player_name} ended.`
},
}
}
export const actionsList = [
getNewAction(),
getStopAction(),
getStfuAction(),
getRestartAction(),
getClearChatAction(),
getGoToPlayerAction(),
getFollowPlayerAction(),
getGoToCoordinatesAction(),
getSearchForBlockAction(),
getSearchForEntityAction(),
getMoveAwayAction(),
getRememberHereAction(),
getGoToRememberedPlaceAction(),
getGivePlayerAction(),
getConsumeAction(),
getEquipAction(),
getPutInChestAction(),
getTakeFromChestAction(),
getViewChestAction(),
getDiscardAction(),
getCollectBlocksAction(),
getCraftRecipeAction(),
getSmeltItemAction(),
getClearFurnaceAction(),
getPlaceHereAction(),
getAttackAction(),
getAttackPlayerAction(),
getGoToBedAction(),
getActivateAction(),
getStayAction(),
getSetModeAction(),
getGoalAction(),
getEndGoalAction(),
getStartConversationAction(),
getEndConversationAction(),
// getEndConversationAction(): Action {
// return {
// name: 'endConversation',
// description: 'End the conversation with the given player.',
// schema: z.object({
// player_name: z.string().describe('The name of the player to end the conversation with.'),
// }),
// perform: (ctx: BotContext) => async (player_name: string) => {
// if (!convoManager.inConversation(player_name))
// return `Not in conversation with ${player_name}.`
// convoManager.endConversation(player_name)
// return `Converstaion with ${player_name} ended.`
// },
// }
// },
]
+20 -2
View File
@@ -3,7 +3,8 @@ import type { BotContext } from '../composables/bot'
import { useLogg } from '@guiiai/logg'
import { agent, neuri } from 'neuri'
import { openaiConfig } from '../composables/config'
import { queryList } from './queries'
import { actionsList } from './actions'
import { queriesList } from './queries'
const agents = new Set<Agent | Promise<Agent>>()
@@ -14,6 +15,7 @@ export async function initAgent(ctx: BotContext): Promise<Neuri> {
let n = neuri()
agents.add(initQueryAgent(ctx))
agents.add(initActionAgent(ctx))
agents.forEach(agent => n = n.agent(agent))
@@ -29,7 +31,7 @@ export async function initQueryAgent(ctx: BotContext): Promise<Agent> {
logger.log('Initializing query agent')
let queryAgent = agent('query')
queryList.forEach((query) => {
Object.values(queriesList).forEach((query) => {
queryAgent = queryAgent.tool(
query.name,
query.schema,
@@ -40,3 +42,19 @@ export async function initQueryAgent(ctx: BotContext): Promise<Agent> {
return queryAgent.build()
}
export async function initActionAgent(ctx: BotContext): Promise<Agent> {
logger.log('Initializing action agent')
let actionAgent = agent('action')
Object.values(actionsList).forEach((action) => {
actionAgent = actionAgent.tool(
action.name,
action.schema,
action.perform(ctx),
{ description: action.description },
)
})
return actionAgent.build()
}
+59 -82
View File
@@ -1,7 +1,6 @@
import type { BotContext } from '../composables/bot'
import { z } from 'zod'
import { getStatusToString } from '../components/status'
import * as world from '../composables/world'
// Core types
type QueryResult = string | Promise<string>
@@ -24,93 +23,71 @@ function formatWearingItem(slot: string, item: string | undefined): string {
return item ? `\n${slot}: ${item}` : ''
}
// Query implementations
function createStatsQuery(): Query {
return {
export const queriesList: Query[] = [
{
name: 'stats',
description: 'Get your bot\'s location, health, hunger, and time of day.',
schema: z.object({}),
perform: (ctx: BotContext) => (): string => getStatusToString(ctx),
}
}
},
// {
// name: 'inventory',
// description: 'Get your bot\'s inventory.',
// schema: z.object({}),
// perform: (ctx: BotContext) => (): string => {
// const { bot } = ctx
// const inventory = world.getInventoryCounts({ bot, botCtx: ctx })
// const items = Object.entries(inventory)
// .map(([item, count]) => formatInventoryItem(item, count))
// .join('')
function createInventoryQuery(): Query {
return {
name: 'inventory',
description: 'Get your bot\'s inventory.',
schema: z.object({}),
perform: (ctx: BotContext) => (): string => {
const { bot } = ctx
const inventory = world.getInventoryCounts({ bot, botCtx: ctx })
const items = Object.entries(inventory)
.map(([item, count]) => formatInventoryItem(item, count))
.join('')
// const wearing = [
// formatWearingItem('Head', bot.inventory.slots[5]?.name),
// formatWearingItem('Torso', bot.inventory.slots[6]?.name),
// formatWearingItem('Legs', bot.inventory.slots[7]?.name),
// formatWearingItem('Feet', bot.inventory.slots[8]?.name),
// ].filter(Boolean).join('')
const wearing = [
formatWearingItem('Head', bot.inventory.slots[5]?.name),
formatWearingItem('Torso', bot.inventory.slots[6]?.name),
formatWearingItem('Legs', bot.inventory.slots[7]?.name),
formatWearingItem('Feet', bot.inventory.slots[8]?.name),
].filter(Boolean).join('')
// return pad(`INVENTORY${items || ': Nothing'}
// ${bot.game.gameMode === 'creative' ? '\n(You have infinite items in creative mode. You do not need to gather resources!!)' : ''}
// WEARING: ${wearing || 'Nothing'}`)
// },
// },
// {
// name: 'nearbyBlocks',
// description: 'Get the blocks near the bot.',
// schema: z.object({}),
// perform: (ctx: BotContext) => (): string => {
// const blocks = world.getNearbyBlockTypes({ bot: ctx.bot, botCtx: ctx })
// return pad(`NEARBY_BLOCKS${blocks.map(b => `\n- ${b}`).join('') || ': none'}`)
// },
// },
// {
// name: 'craftable',
// description: 'Get the craftable items with the bot\'s inventory.',
// schema: z.object({}),
// perform: (ctx: BotContext) => (): string => {
// const craftable = world.getCraftableItems({ bot: ctx.bot, botCtx: ctx })
// return pad(`CRAFTABLE_ITEMS${craftable.map(i => `\n- ${i}`).join('') || ': none'}`)
// },
// },
// {
// name: 'entities',
// description: 'Get the nearby players and entities.',
// schema: z.object({}),
// perform: (ctx: BotContext) => (): string => {
// const { bot } = ctx
// const worldCtx = { bot, botCtx: ctx }
// const players = world.getNearbyPlayerNames(worldCtx)
// const entities = world.getNearbyEntityTypes(worldCtx)
// .filter((e: string) => e !== 'player' && e !== 'item')
return pad(`INVENTORY${items || ': Nothing'}
${bot.game.gameMode === 'creative' ? '\n(You have infinite items in creative mode. You do not need to gather resources!!)' : ''}
WEARING: ${wearing || 'Nothing'}`)
},
}
}
// const result = [
// ...players.map((p: string) => `- Human player: ${p}`),
// ...entities.map((e: string) => `- entities: ${e}`),
// ]
function createNearbyBlocksQuery(): Query {
return {
name: 'nearbyBlocks',
description: 'Get the blocks near the bot.',
schema: z.object({}),
perform: (ctx: BotContext) => (): string => {
const blocks = world.getNearbyBlockTypes({ bot: ctx.bot, botCtx: ctx })
return pad(`NEARBY_BLOCKS${blocks.map(b => `\n- ${b}`).join('') || ': none'}`)
},
}
}
function createCraftableQuery(): Query {
return {
name: 'craftable',
description: 'Get the craftable items with the bot\'s inventory.',
schema: z.object({}),
perform: (ctx: BotContext) => (): string => {
const craftable = world.getCraftableItems({ bot: ctx.bot, botCtx: ctx })
return pad(`CRAFTABLE_ITEMS${craftable.map(i => `\n- ${i}`).join('') || ': none'}`)
},
}
}
function createEntitiesQuery(): Query {
return {
name: 'entities',
description: 'Get the nearby players and entities.',
schema: z.object({}),
perform: (ctx: BotContext) => (): string => {
const { bot } = ctx
const worldCtx = { bot, botCtx: ctx }
const players = world.getNearbyPlayerNames(worldCtx)
const entities = world.getNearbyEntityTypes(worldCtx)
.filter((e: string) => e !== 'player' && e !== 'item')
const result = [
...players.map((p: string) => `- Human player: ${p}`),
...entities.map((e: string) => `- entities: ${e}`),
]
return pad(`NEARBY_ENTITIES${result.length ? `\n${result.join('\n')}` : ': none'}`)
},
}
}
// Export query list
export const queryList: readonly Query[] = [
createStatsQuery(),
// createInventoryQuery(),
// createNearbyBlocksQuery(),
// createCraftableQuery(),
// createEntitiesQuery(),
// return pad(`NEARBY_ENTITIES${result.length ? `\n${result.join('\n')}` : ': none'}`)
// },
// },
] as const
+81
View File
@@ -6,6 +6,87 @@ import * as mc from '../utils/mcdata'
import { log } from './base'
import { goToPosition } from './movement'
export async function collectBlock(
bot: Bot,
blockType: string,
num: number = 1,
exclude: typeof Vec3[] | null = null,
): Promise<boolean> {
if (num < 1) {
log(bot, `Invalid number of blocks to collect: ${num}.`)
return false
}
const blocktypes: string[] = [blockType]
if (blockType === 'coal' || blockType === 'diamond' || blockType === 'emerald'
|| blockType === 'iron' || blockType === 'gold' || blockType === 'lapis_lazuli'
|| blockType === 'redstone') {
blocktypes.push(`${blockType}_ore`)
}
if (blockType.endsWith('ore')) {
blocktypes.push(`deepslate_${blockType}`)
}
if (blockType === 'dirt') {
blocktypes.push('grass_block')
}
let collected = 0
for (let i = 0; i < num; i++) {
let blocks = world.getNearestBlocks(bot, blocktypes, 64)
if (exclude) {
blocks = blocks.filter(
block => !exclude.some(pos =>
pos.x === block.position.x
&& pos.y === block.position.y
&& pos.z === block.position.z,
),
)
}
const movements = new bot.pathfinder.Movements(bot)
movements.dontMineUnderFallingBlock = false
blocks = blocks.filter(block => movements.safeToBreak(block))
if (blocks.length === 0) {
log(bot, collected === 0
? `No ${blockType} nearby to collect.`
: `No more ${blockType} nearby to collect.`)
break
}
const block = blocks[0]
await bot.tool.equipForBlock(block)
const itemId = bot.heldItem ? bot.heldItem.type : null
if (!block.canHarvest(itemId)) {
log(bot, `Don't have right tools to harvest ${blockType}.`)
return false
}
try {
await bot.collectBlock.collect(block)
collected++
await autoLight(bot)
}
catch (err) {
if (err.name === 'NoChests') {
log(bot, `Failed to collect ${blockType}: Inventory full, no place to deposit.`)
break
}
log(bot, `Failed to collect ${blockType}: ${err}.`)
continue
}
if (bot.interrupt_code) {
break
}
}
log(bot, `Collected ${collected} ${blockType}.`)
return collected > 0
}
/**
* Place a torch if needed
*/
+3 -3
View File
@@ -1,8 +1,8 @@
import type { Bot } from 'mineflayer'
import type { Entity } from 'prismarine-entity'
import * as mc from '../../../utils/mcdata.js'
import * as world from '../world.js'
import { log } from './base.js'
import * as world from '../composables/world'
import * as mc from '../utils/mcdata'
import { log } from './base'
/**
* Equip the item with highest attack damage
+6 -50
View File
@@ -1,50 +1,6 @@
// Base utilities
export { log } from './base'
export type { BlockFace, Position } from './base'
// Block interaction functions
export {
activateNearestBlock,
breakBlockAt,
placeBlock,
tillAndSow,
useDoor,
} from './blocks.js'
// Combat related functions
export {
attackEntity,
attackNearest,
defendSelf,
} from './combat.js'
// Crafting and smelting functions
export {
clearNearestFurnace,
craftRecipe,
smeltItem,
} from './crafting.js'
// Inventory management functions
export {
consume,
discard,
equip,
giveToPlayer,
pickupNearbyItems,
putInChest,
takeFromChest,
viewChest,
} from './inventory.js'
// Movement related functions
export {
followPlayer,
goToNearestBlock,
goToNearestEntity,
goToPlayer,
goToPosition,
moveAway,
moveAwayFromEntity,
stay,
} from './movement.js'
export * from './base'
export * from './blocks.js'
export * from './combat.js'
export * from './crafting.js'
export * from './inventory.js'
export * from './movement.js'
+31
View File
@@ -206,3 +206,34 @@ export async function stay(bot: Bot, seconds = 30): Promise<boolean> {
log(bot, `Stayed for ${(Date.now() - start) / 1000} seconds.`)
return true
}
/**
* Sleep in the nearest bed within 32 blocks
*/
export async function goToBed(bot: Bot): Promise<boolean> {
const beds: Vec3[] = bot.findBlocks({
matching: (block: Block) => block.name.includes('bed'),
maxDistance: 32,
count: 1,
})
if (beds.length === 0) {
log(bot, 'Could not find a bed to sleep in.')
return false
}
const loc: Vec3 = beds[0]
await goToPosition(bot, loc.x, loc.y, loc.z)
const bed: Block | null = bot.blockAt(loc)
await bot.sleep(bed)
log(bot, 'You are in bed.')
bot.modes.pause('unstuck')
while (bot.isSleeping) {
await new Promise(resolve => setTimeout(resolve, 500))
}
log(bot, 'You have woken up.')
return true
}