feat(minecraft): add pause/resume control for cognitive engine via !pause command and Brain.setPaused/togglePaused/isPaused methods
Add paused state to Brain with setPaused/togglePaused/isPaused methods, suppress all event processing while paused (log suppression to llmLogEntries with scheduler/paused/suppressed tags), add !pause chat command in CognitiveEngine to toggle pause state with confirmation message, expose paused flag in getReplState/getSnapshot for debug visibility, add test verifying ll
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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<void> {
|
||||
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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -46,6 +46,7 @@ describe('mcpReplServer', () => {
|
||||
queueLength: 0,
|
||||
turnCounter: 1,
|
||||
giveUpUntil: 0,
|
||||
paused: false,
|
||||
contextView: 'test context',
|
||||
conversationHistory: [],
|
||||
llmLogEntries: [],
|
||||
|
||||
@@ -55,6 +55,7 @@ export class McpReplServer {
|
||||
queueLength: snapshot.queueLength,
|
||||
turn: snapshot.turnCounter,
|
||||
giveUpUntil: snapshot.giveUpUntil,
|
||||
paused: snapshot.paused,
|
||||
}, null, 2),
|
||||
mimeType: 'application/json',
|
||||
}],
|
||||
|
||||
Reference in New Issue
Block a user