From e69e6608b8a2c2b37ff28e27487fcde0b3ebb0fe Mon Sep 17 00:00:00 2001 From: Rin Date: Wed, 4 Feb 2026 00:19:24 +0800 Subject: [PATCH] feat(minecraft): add giveUp tool with cooldown suppression --- .../src/cognitive/action/llm-actions.ts | 10 +++++ .../src/cognitive/conscious/brain.ts | 44 +++++++++++++++++++ .../conscious/prompts/brain-prompt.ts | 1 + 3 files changed, 55 insertions(+) diff --git a/services/minecraft/src/cognitive/action/llm-actions.ts b/services/minecraft/src/cognitive/action/llm-actions.ts index 4c756a9f..bd40f101 100644 --- a/services/minecraft/src/cognitive/action/llm-actions.ts +++ b/services/minecraft/src/cognitive/action/llm-actions.ts @@ -37,6 +37,16 @@ export const actionsList: Action[] = [ return `Sent message: "${message}"` }, }, + { + name: 'giveUp', + description: 'Admit you are currently stuck and pause autonomous retries for a cooldown window.', + execution: 'sync', + schema: z.object({ + reason: z.string().min(1).describe('Short explanation of why you are stuck.'), + cooldown_seconds: z.number().int().min(10).max(600).default(45).describe('How long to pause retries before re-evaluating.'), + }), + perform: () => (reason: string, cooldown_seconds: number): string => `Gave up for ${cooldown_seconds}s: ${reason}`, + }, // {\n // name: 'setReflexMode', // description: 'Set (or clear) your reflex mode override. Use work/wander to disable idle-only reflex behaviors. Set override to null to return to automatic mode selection.', // execution: 'sequential', diff --git a/services/minecraft/src/cognitive/conscious/brain.ts b/services/minecraft/src/cognitive/conscious/brain.ts index 48770427..913a0904 100644 --- a/services/minecraft/src/cognitive/conscious/brain.ts +++ b/services/minecraft/src/cognitive/conscious/brain.ts @@ -44,6 +44,8 @@ export class Brain { private queue: QueuedEvent[] = [] private isProcessing = false private currentCancellationToken: CancellationToken | undefined + private giveUpUntil = 0 + private giveUpReason: string | undefined private lastContextView: string | undefined private conversationHistory: Message[] = [] @@ -68,6 +70,13 @@ 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 === 'giveUp') { + const secondsRaw = Number(action.params?.cooldown_seconds ?? 45) + const cooldownSeconds = Number.isFinite(secondsRaw) ? Math.min(600, Math.max(10, Math.floor(secondsRaw))) : 45 + this.giveUpUntil = Date.now() + cooldownSeconds * 1000 + this.giveUpReason = typeof action.params?.reason === 'string' ? action.params.reason : undefined + } + this.enqueueEvent(bot, { type: 'feedback', payload: { status: 'success', action, result }, @@ -139,6 +148,10 @@ export class Brain { // --- Cognitive Cycle --- private async processEvent(bot: MineflayerWithAgents, event: BotEvent): Promise { + this.resumeFromGiveUpIfNeeded(event) + if (this.shouldSuppressDuringGiveUp(event)) + return + // 0. Build Context View const snapshot = this.deps.reflexManager.getContextSnapshot() const view = buildConsciousContextView(snapshot) @@ -314,8 +327,39 @@ export class Brain { // Note: We don't update this.lastContextView here; caller does it after building message } + if (this.giveUpUntil > Date.now()) { + const remainingSec = Math.max(0, Math.ceil((this.giveUpUntil - Date.now()) / 1000)) + parts.push(`[STATE] giveUp active (${remainingSec}s left). reason=${this.giveUpReason ?? 'unknown'}`) + } + parts.push('[RUNTIME] Globals are refreshed every turn: snapshot, self, environment, social, threat, attention, event, now, mem, lastRun, lastAction.') return parts.join('\n\n') } + + private shouldSuppressDuringGiveUp(event: BotEvent): boolean { + if (Date.now() >= this.giveUpUntil) + return false + + if (event.type !== 'perception') + return true + + const signal = event.payload as PerceptionSignal + return signal.type !== 'chat_message' + } + + private resumeFromGiveUpIfNeeded(event: BotEvent): void { + if (Date.now() >= this.giveUpUntil) + return + + if (event.type !== 'perception') + return + + const signal = event.payload as PerceptionSignal + if (signal.type !== 'chat_message') + return + + this.giveUpUntil = 0 + this.giveUpReason = undefined + } } diff --git a/services/minecraft/src/cognitive/conscious/prompts/brain-prompt.ts b/services/minecraft/src/cognitive/conscious/prompts/brain-prompt.ts index 443d73bc..c15f7914 100644 --- a/services/minecraft/src/cognitive/conscious/prompts/brain-prompt.ts +++ b/services/minecraft/src/cognitive/conscious/prompts/brain-prompt.ts @@ -109,6 +109,7 @@ Examples: - Treat action results as potentially unreliable; check outcomes against \`snapshot\`/feedback before committing to the next step. - Prefer deterministic scripts: no random branching unless needed. - Keep per-turn scripts short and focused on one tactical objective. +- If you hit repeated failures with no progress, call \`await giveUp({ reason, cooldown_seconds })\` once instead of retry-spamming. # Rules - **Native Reasoning**: You can think before outputting your action.