fix(minecraft): clamp unsolicited chat and ignore harness wrappers
This commit is contained in:
@@ -31,6 +31,7 @@ export const actionsList: Action[] = [
|
||||
execution: 'sync',
|
||||
schema: z.object({
|
||||
message: z.string().describe('The message to send in chat.'),
|
||||
feedback: z.boolean().default(false).describe('Whether to emit FEEDBACK for this chat action. Keep false for normal conversation to avoid feedback loops.'),
|
||||
}),
|
||||
perform: mineflayer => (message: string): string => {
|
||||
mineflayer.bot.chat(message)
|
||||
|
||||
@@ -46,6 +46,8 @@ export class Brain {
|
||||
private currentCancellationToken: CancellationToken | undefined
|
||||
private giveUpUntil = 0
|
||||
private giveUpReason: string | undefined
|
||||
private lastHumanChatAt = 0
|
||||
private botUsername = ''
|
||||
private lastContextView: string | undefined
|
||||
private conversationHistory: Message[] = []
|
||||
|
||||
@@ -55,6 +57,7 @@ export class Brain {
|
||||
|
||||
public init(bot: MineflayerWithAgents): void {
|
||||
this.deps.logger.log('INFO', 'Brain: Initializing stateful core...')
|
||||
this.botUsername = bot.bot.username
|
||||
|
||||
// Perception Handler
|
||||
this.deps.eventBus.subscribe<PerceptionSignal>('conscious:signal:*', (event: TracedEvent<PerceptionSignal>) => {
|
||||
@@ -70,6 +73,10 @@ export class Brain {
|
||||
this.deps.taskExecutor.on('action:completed', async ({ action, result }) => {
|
||||
this.deps.logger.log('INFO', `Brain: Action completed: ${action.tool}`)
|
||||
|
||||
if (action.tool === 'chat' && action.params?.feedback !== true) {
|
||||
return
|
||||
}
|
||||
|
||||
if (action.tool === 'giveUp') {
|
||||
const secondsRaw = Number(action.params?.cooldown_seconds ?? 45)
|
||||
const cooldownSeconds = Number.isFinite(secondsRaw) ? Math.min(600, Math.max(10, Math.floor(secondsRaw))) : 45
|
||||
@@ -148,6 +155,7 @@ export class Brain {
|
||||
// --- Cognitive Cycle ---
|
||||
|
||||
private async processEvent(bot: MineflayerWithAgents, event: BotEvent): Promise<void> {
|
||||
this.updateHumanChatTimestamp(event)
|
||||
this.resumeFromGiveUpIfNeeded(event)
|
||||
if (this.shouldSuppressDuringGiveUp(event))
|
||||
return
|
||||
@@ -254,6 +262,10 @@ export class Brain {
|
||||
this.deps.taskExecutor.getAvailableActions(),
|
||||
{ event, snapshot: snapshot as unknown as Record<string, unknown> },
|
||||
async (action: ActionInstruction) => {
|
||||
if (action.tool === 'chat' && !this.shouldAllowChatForEvent(event, snapshot.self.health)) {
|
||||
return 'Chat suppressed: no direct user prompt for chat this turn'
|
||||
}
|
||||
|
||||
const actionDef = actionDefs.get(action.tool)
|
||||
const isPhysicalAction = action.tool !== 'skip' && !actionDef?.readonly
|
||||
|
||||
@@ -362,4 +374,35 @@ export class Brain {
|
||||
this.giveUpUntil = 0
|
||||
this.giveUpReason = undefined
|
||||
}
|
||||
|
||||
private shouldAllowChatForEvent(event: BotEvent, health: number): boolean {
|
||||
if (health <= 8)
|
||||
return true
|
||||
|
||||
if (event.type !== 'perception')
|
||||
return Date.now() - this.lastHumanChatAt <= 45000 && event.type === 'feedback'
|
||||
|
||||
const signal = event.payload as PerceptionSignal
|
||||
if (signal.type === 'chat_message') {
|
||||
const speaker = typeof (signal.metadata as any)?.username === 'string'
|
||||
? String((signal.metadata as any).username)
|
||||
: signal.sourceId
|
||||
if (speaker === this.botUsername)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private updateHumanChatTimestamp(event: BotEvent): void {
|
||||
if (event.type !== 'perception')
|
||||
return
|
||||
|
||||
const signal = event.payload as PerceptionSignal
|
||||
if (signal.type !== 'chat_message')
|
||||
return
|
||||
|
||||
this.lastHumanChatAt = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,5 +143,9 @@ Examples:
|
||||
- **Handling Feedback**: When you perform an action, you will see a \`[FEEDBACK]\` message in the history later with the result. Use this to verify success.
|
||||
- **Tool Choice**: If a dedicated tool exists for a task, use it.
|
||||
- **Skip Rule**: If you call \`skip()\`, do not call any other tool in the same turn.
|
||||
- **Chat Discipline**: Do not send proactive small-talk. Use \`chat\` only when replying to a player chat, reporting meaningful task progress/failure, or urgent safety status.
|
||||
- **No Harness Replies**: Never treat \`[PERCEPTION]\`, \`[FEEDBACK]\`, or other system wrappers as players. Only reply with \`chat\` to actual player \`chat_message\` events.
|
||||
- **No Self Replies**: Never reply to your own previous bot messages.
|
||||
- **Chat Feedback**: \`chat\` feedback is optional; keep \`feedback: false\` for normal conversation. Use \`feedback: true\` only when your next step explicitly needs the chat acknowledgement in history.
|
||||
`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user