diff --git a/services/minecraft/src/cognitive/conscious/brain.test.ts b/services/minecraft/src/cognitive/conscious/brain.test.ts index 129c0d71..f66b05d9 100644 --- a/services/minecraft/src/cognitive/conscious/brain.test.ts +++ b/services/minecraft/src/cognitive/conscious/brain.test.ts @@ -162,6 +162,19 @@ inv; expect(enqueueSpy).not.toHaveBeenCalled() }) + it('suppresses llm turns while paused', async () => { + const deps: any = createDeps('await chat("hi")') + const brain: any = new Brain(deps) + const enqueueSpy = vi.fn(async () => undefined) + brain.enqueueEvent = enqueueSpy + brain.setPaused(true) + + await brain.processEvent({} as any, createPerceptionEvent()) + + expect(deps.llmAgent.callLLM).not.toHaveBeenCalled() + expect(enqueueSpy).not.toHaveBeenCalled() + }) + it('refreshes reflex context before debug perception injection', async () => { const deps: any = createDeps('await skip()') deps.reflexManager.refreshFromBotState = vi.fn() diff --git a/services/minecraft/src/cognitive/conscious/brain.ts b/services/minecraft/src/cognitive/conscious/brain.ts index 132b202d..ae0c026a 100644 --- a/services/minecraft/src/cognitive/conscious/brain.ts +++ b/services/minecraft/src/cognitive/conscious/brain.ts @@ -142,6 +142,7 @@ const NO_ACTION_FOLLOWUP_SOURCE_ID = 'brain:no_action_followup' export class Brain { private debugService: DebugService private readonly planner = new JavaScriptPlanner() + private paused = false // State private queue: QueuedEvent[] = [] @@ -247,7 +248,7 @@ export class Brain { this.runtimeMineflayer = null } - public getReplState(): { variables: PlannerGlobalDescriptor[], updatedAt: number } { + public getReplState(): { variables: PlannerGlobalDescriptor[], updatedAt: number, paused: boolean } { const snapshot = this.deps.reflexManager.getContextSnapshot() const replEvent: BotEvent = { type: 'system_alert', @@ -263,6 +264,7 @@ export class Brain { return { variables, updatedAt: Date.now(), + paused: this.paused, } } @@ -271,6 +273,7 @@ export class Brain { queueLength: number turnCounter: number giveUpUntil: number + paused: boolean contextView: string | undefined conversationHistory: Message[] llmLogEntries: LlmLogEntry[] @@ -280,12 +283,26 @@ export class Brain { queueLength: this.queue.length, turnCounter: this.turnCounter, giveUpUntil: this.giveUpUntil, + paused: this.paused, contextView: this.lastContextView, conversationHistory: this.cloneMessages(this.conversationHistory), llmLogEntries: [...this.llmLogEntries], } } + public setPaused(paused: boolean): boolean { + this.paused = paused + return this.paused + } + + public togglePaused(): boolean { + return this.setPaused(!this.paused) + } + + public isPaused(): boolean { + return this.paused + } + public getLastLlmInput(): LlmInputSnapshot | null { if (!this.lastLlmInputSnapshot) return null @@ -616,6 +633,20 @@ export class Brain { // --- Cognitive Cycle --- private async processEvent(bot: MineflayerWithAgents, event: BotEvent): Promise { + if (this.paused) { + this.appendLlmLog({ + turnId: this.turnCounter, + kind: 'scheduler', + eventType: event.type, + sourceType: event.source.type, + sourceId: event.source.id, + tags: ['scheduler', 'paused', 'suppressed'], + text: `Suppressed event while paused: ${event.type} from ${event.source.type}:${event.source.id}`, + }) + this.deps.logger.log('INFO', `Brain: Ignoring event while paused (${event.type} from ${event.source.type}:${event.source.id})`) + return + } + this.resumeFromGiveUpIfNeeded(event) if (this.shouldSuppressDuringGiveUp(event)) return diff --git a/services/minecraft/src/cognitive/index.ts b/services/minecraft/src/cognitive/index.ts index ea285e2b..438e0d48 100644 --- a/services/minecraft/src/cognitive/index.ts +++ b/services/minecraft/src/cognitive/index.ts @@ -108,6 +108,12 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug if (chatHandler.isBotMessage(username)) return + if (message.trim().toLowerCase() === '!pause') { + const paused = brain.togglePaused() + bot.bot.chat(`[debug] Cognitive engine ${paused ? 'paused' : 'resumed'} (by ${username}).`) + return + } + // Bridge chat directly into EventBus as a signal so Reflex can react to it. eventBus.emit({ type: 'signal:chat_message', diff --git a/services/minecraft/src/debug/mcp-repl-server.test.ts b/services/minecraft/src/debug/mcp-repl-server.test.ts index f711ca2c..85787c65 100644 --- a/services/minecraft/src/debug/mcp-repl-server.test.ts +++ b/services/minecraft/src/debug/mcp-repl-server.test.ts @@ -46,6 +46,7 @@ describe('mcpReplServer', () => { queueLength: 0, turnCounter: 1, giveUpUntil: 0, + paused: false, contextView: 'test context', conversationHistory: [], llmLogEntries: [], diff --git a/services/minecraft/src/debug/mcp-repl-server.ts b/services/minecraft/src/debug/mcp-repl-server.ts index 5d7c43ea..4d3d124c 100644 --- a/services/minecraft/src/debug/mcp-repl-server.ts +++ b/services/minecraft/src/debug/mcp-repl-server.ts @@ -55,6 +55,7 @@ export class McpReplServer { queueLength: snapshot.queueLength, turn: snapshot.turnCounter, giveUpUntil: snapshot.giveUpUntil, + paused: snapshot.paused, }, null, 2), mimeType: 'application/json', }],