feat(minecraft): work mode in reflex if action inflight, so idle behaviors doesn't get played while working
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user