feat(minecraft): work mode in reflex if action inflight, so idle behaviors doesn't get played while working

This commit is contained in:
Rin
2026-02-18 11:12:06 +08:00
committed by Neko Ayaka
parent c4b35f650a
commit b11bb1a7c8
8 changed files with 79 additions and 4 deletions
@@ -25,6 +25,24 @@ function formatWearingItem(slot: string, item: string | undefined): string {
}
export const actionsList: Action[] = [
// {
// 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',
// schema: z.object({
// mode: z.enum(['idle', 'social', 'alert', 'work', 'wander']).nullable().describe('Mode override to set'),
// }),
// perform: mineflayer => async (mode: 'idle' | 'social' | 'alert' | 'work' | 'wander' | null) => {
// const reflexManager = (mineflayer as any).reflexManager
// if (!reflexManager || typeof reflexManager.setModeOverride !== 'function')
// throw new Error('ReflexManager is not available on this bot. Is CognitiveEngine enabled?')
// reflexManager.setModeOverride(mode)
// return mode
// ? `Reflex mode override set to '${mode}'.`
// : 'Reflex mode override cleared (automatic mode selection resumed).'
// },
// },
{
name: 'inventory',
description: 'Get your inventory.',
@@ -117,10 +117,11 @@ export function createAgentContainer(options: {
}),
// Reflex Manager (Reactive Layer)
reflexManager: asFunction(({ eventBus, perceptionPipeline, logger }) =>
reflexManager: asFunction(({ eventBus, perceptionPipeline, taskExecutor, logger }) =>
new ReflexManager({
eventBus,
perception: perceptionPipeline.getPerceptionAPI(),
taskExecutor,
logger,
}),
).singleton(),
@@ -38,6 +38,7 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
const botWithAgents = bot as unknown as MineflayerWithAgents
botWithAgents.action = actionAgent
botWithAgents.chat = chatAgent
botWithAgents.reflexManager = reflexManager
const startCognitive = () => {
if (started)
@@ -1,6 +1,6 @@
import type { ReflexContextState } from './context'
export type ReflexModeId = 'idle' | 'social' | 'alert'
export type ReflexModeId = 'idle' | 'social' | 'alert' | 'work' | 'wander'
export function selectMode(ctx: ReflexContextState): ReflexModeId {
if (ctx.threat.threatScore > 0)
@@ -41,6 +41,12 @@ describe('reflexManager', () => {
emitChild: vi.fn(),
} as any
const taskExecutor = {
on: vi.fn(),
off: vi.fn(),
removeListener: vi.fn(),
} as any
const logger = makeLogger()
const perception = {
getPlayers: vi.fn(() => []),
@@ -49,7 +55,7 @@ describe('reflexManager', () => {
updateEntity: vi.fn(),
updateSelfPosition: vi.fn(),
} as any
const reflex = new ReflexManager({ eventBus, perception, logger })
const reflex = new ReflexManager({ eventBus, perception, taskExecutor, logger })
const bot = makeBot()
reflex.init(bot)
@@ -1,5 +1,6 @@
import type { Logg } from '@guiiai/logg'
import type { TaskExecutor } from '../action/task-executor'
import type { EventBus, TracedEvent } from '../os'
import type { PerceptionAPI } from '../perception/perception-api'
import type { PerceptionSignal } from '../perception/types/signals'
@@ -16,11 +17,14 @@ export class ReflexManager {
private bot: MineflayerWithAgents | null = null
private readonly runtime: ReflexRuntime
private unsubscribe: (() => void) | null = null
private unsubscribeTaskExecutor: (() => void) | null = null
private inFlightActionsCount = 0
constructor(
private readonly deps: {
eventBus: EventBus
perception: PerceptionAPI
taskExecutor: TaskExecutor
logger: Logg
},
) {
@@ -39,6 +43,32 @@ export class ReflexManager {
this.unsubscribe = this.deps.eventBus.subscribe('signal:*', (event) => {
this.onSignal(event as TracedEvent<PerceptionSignal>)
})
const onStarted = () => {
if (this.inFlightActionsCount === 0)
this.runtime.setMode('work')
this.inFlightActionsCount++
}
const onEnded = () => {
this.inFlightActionsCount = Math.max(0, this.inFlightActionsCount - 1)
if (this.inFlightActionsCount === 0)
this.runtime.setMode('idle')
}
this.deps.taskExecutor.on('action:started', onStarted)
this.deps.taskExecutor.on('action:completed', onEnded)
this.deps.taskExecutor.on('action:failed', onEnded)
this.unsubscribeTaskExecutor = () => {
// Node's EventEmitter supports off() but we keep a fallback for compatibility.
; (this.deps.taskExecutor as any).off?.('action:started', onStarted)
; (this.deps.taskExecutor as any).off?.('action:completed', onEnded)
; (this.deps.taskExecutor as any).off?.('action:failed', onEnded)
; (this.deps.taskExecutor as any).removeListener?.('action:started', onStarted)
; (this.deps.taskExecutor as any).removeListener?.('action:completed', onEnded)
; (this.deps.taskExecutor as any).removeListener?.('action:failed', onEnded)
}
}
public destroy(): void {
@@ -46,6 +76,11 @@ export class ReflexManager {
this.unsubscribe()
this.unsubscribe = null
}
if (this.unsubscribeTaskExecutor) {
this.unsubscribeTaskExecutor()
this.unsubscribeTaskExecutor = null
}
this.inFlightActionsCount = 0
this.bot = null
}
@@ -53,6 +88,10 @@ export class ReflexManager {
return this.runtime.getContext().getSnapshot()
}
public getMode(): ReturnType<ReflexRuntime['getMode']> {
return this.runtime.getMode()
}
public updateEnvironment(patch: Partial<ReflexContextState['environment']>): void {
this.runtime.getContext().updateEnvironment(patch)
}
@@ -31,6 +31,10 @@ export class ReflexRuntime {
return this.mode
}
public setMode(mode: ReflexModeId): void {
this.mode = mode
}
public getActiveBehaviorId(): string | null {
return this.activeBehaviorId
}
@@ -64,7 +68,11 @@ export class ReflexRuntime {
.map(name => ({ name })),
})
this.mode = selectMode(this.context.getSnapshot())
// Allow explicit modes like 'work' / 'wander' to remain until changed by caller.
// Otherwise, compute from context automatically.
// TODO: consider letting 'alert' preempt work/wander so survival can override tasks.
if (this.mode !== 'work' && this.mode !== 'wander')
this.mode = selectMode(this.context.getSnapshot())
if (this.activeBehaviorUntil && now < this.activeBehaviorUntil)
return null
@@ -3,6 +3,7 @@ import type { Neuri } from 'neuri'
import type { Mineflayer } from '../../libs/mineflayer'
import type { ActionAgent, ChatAgent, PlanningAgent } from '../../libs/mineflayer/base-agent'
import type { ReflexManager } from '../reflex/reflex-manager'
export interface LLMConfig {
agent: Neuri
@@ -21,6 +22,7 @@ export interface MineflayerWithAgents extends Mineflayer {
planning: PlanningAgent
action: ActionAgent
chat: ChatAgent
reflexManager: ReflexManager
}
export interface CognitiveEngineOptions {