refactor(minecraft): migrate from EventManager to unified EventBus for perception signals

This commit is contained in:
Rin
2026-02-18 11:12:19 +08:00
committed by Neko Ayaka
parent e02f718aa2
commit ca82290064
6 changed files with 22 additions and 93 deletions
@@ -45,14 +45,14 @@ describe('brain decide retry', () => {
} as any
const brain = new Brain({
eventManager: {} as any,
eventBus: { subscribe: vi.fn() } as any,
neuri,
logger,
taskExecutor: { getAvailableActions: () => [] } as any,
reflexManager: { getContextSnapshot: () => ({}) } as any,
})
; (brain as any).bot = { bot: { chat: vi.fn() } }
; (brain as any).bot = { bot: { chat: vi.fn() } }
const res = await (brain as any).decide('sys', 'user')
expect(res?.thought).toBe('ok')
@@ -77,7 +77,7 @@ describe('brain decide retry', () => {
} as any
const brain = new Brain({
eventManager: {} as any,
eventBus: { subscribe: vi.fn() } as any,
neuri,
logger,
taskExecutor: { getAvailableActions: () => [] } as any,
@@ -85,7 +85,7 @@ describe('brain decide retry', () => {
})
const chat = vi.fn()
; (brain as any).bot = { bot: { chat } }
; (brain as any).bot = { bot: { chat } }
await expect((brain as any).decide('sys', 'user')).rejects.toThrow('unauthorized')
expect(neuri.handleStateless).toHaveBeenCalledTimes(1)
@@ -3,7 +3,7 @@ import type { Neuri } from 'neuri'
import type { TaskExecutor } from '../action/task-executor'
import type { ActionInstruction } from '../action/types'
import type { EventManager } from '../perception/event-manager'
import type { EventBus, TracedEvent } from '../os'
import type { PerceptionSignal } from '../perception/types/signals'
import type { ReflexManager } from '../reflex/reflex-manager'
import type { BotEvent, MineflayerWithAgents } from '../types'
@@ -82,7 +82,7 @@ function isLikelyRecoverableError(err: unknown): boolean {
}
interface BrainDeps {
eventManager: EventManager
eventBus: EventBus
neuri: Neuri
logger: Logg
taskExecutor: TaskExecutor
@@ -161,10 +161,7 @@ export class Brain {
this.bot = bot
this.blackboard.update({ selfUsername: bot.username })
// Perception Signal Handler - Only process chat messages for now
this.deps.eventManager.on<PerceptionSignal>('perception', async (event) => {
const signal = event.payload
// Only handle chat messages in the deliberative layer
const handleSignal = async (signal: PerceptionSignal) => {
if (signal.type !== 'chat_message' && signal.type !== 'social_presence')
return
@@ -172,8 +169,17 @@ export class Brain {
await this.handlePerceptionSignal(bot, signal)
}
catch (err) {
this.log('ERROR', `Brain: Failed to enqueue chat event`, { error: err })
this.log('ERROR', 'Brain: Failed to enqueue chat event', { error: err })
}
}
// Perception Signal Handler - unified on EventBus
this.deps.eventBus.subscribe<PerceptionSignal>('signal:chat_message', (event: TracedEvent<PerceptionSignal>) => {
void handleSignal(event.payload)
})
this.deps.eventBus.subscribe<PerceptionSignal>('signal:social_presence', (event: TracedEvent<PerceptionSignal>) => {
void handleSignal(event.payload)
})
// Listen to Task Execution Events (Action Feedback)
@@ -12,7 +12,6 @@ import { PlanningAgentImpl } from '../agents/planning'
import { TaskExecutor } from './action/task-executor'
import { Brain } from './conscious/brain'
import { createEventBus, createRuleEngine } from './os'
import { EventManager } from './perception/event-manager'
import { PerceptionPipeline } from './perception/pipeline'
import { ReflexManager } from './reflex/reflex-manager'
@@ -24,7 +23,6 @@ export interface ContainerServices {
planningAgent: PlanningAgentImpl
chatAgent: ChatAgentImpl
neuri: Neuri
eventManager: EventManager
perceptionPipeline: PerceptionPipeline
taskExecutor: TaskExecutor
brain: Brain
@@ -102,8 +100,6 @@ export function createAgentContainer(options: {
idleTimeout: 5 * 60 * 1000, // 5 minutes
})),
eventManager: asClass(EventManager).singleton(),
perceptionPipeline: asClass(PerceptionPipeline).singleton(),
taskExecutor: asClass(TaskExecutor).singleton(),
@@ -112,6 +108,7 @@ export function createAgentContainer(options: {
.singleton()
.inject((c) => {
return {
eventBus: c.resolve('eventBus'),
reflexManager: c.resolve('reflexManager'),
}
}),
+2 -15
View File
@@ -5,7 +5,6 @@ import { config } from '../composables/config'
import { DebugService } from '../debug'
import { ChatMessageHandler } from '../libs/mineflayer'
import { createAgentContainer } from './container'
import { createPerceptionFrameFromChat } from './perception/frame'
import { computeNearbyPlayerGaze } from './reflex/gaze'
export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlugin {
@@ -24,7 +23,6 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
const actionAgent = container.resolve('actionAgent')
const chatAgent = container.resolve('chatAgent')
const perceptionPipeline = container.resolve('perceptionPipeline')
const eventManager = container.resolve('eventManager')
const brain = container.resolve('brain')
const reflexManager = container.resolve('reflexManager')
const taskExecutor = container.resolve('taskExecutor')
@@ -75,16 +73,6 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
// Resolve EventBus and subscribe to forward events to debug timeline
const eventBus = container.resolve('eventBus')
// Bridge selected EventBus signals into EventManager perception stream for Brain.
eventBus.subscribe('signal:social_presence', (event) => {
eventManager.emit({
type: 'perception',
payload: event.payload as any,
source: { type: 'minecraft', id: 'eventBus' },
timestamp: Date.now(),
})
})
eventBus.subscribe('*', (event) => {
// Forward to debug service for timeline visualization
DebugService.getInstance().emitTrace({
@@ -98,14 +86,13 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
})
})
// Set message handling via EventManager
// Set message handling via EventBus
const chatHandler = new ChatMessageHandler(bot.username)
bot.bot.on('chat', (username, message) => {
if (chatHandler.isBotMessage(username))
return
// Bridge chat directly into EventBus as a signal so Reflex can react to it.
// (PerceptionPipeline will also ingest this for Brain via EventManager.)
eventBus.emit({
type: 'signal:chat_message',
payload: Object.freeze({
@@ -125,7 +112,7 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
},
})
perceptionPipeline.ingest(createPerceptionFrameFromChat(username, message))
// Chat is handled via signal:chat_message only; no extra perception emission needed.
})
}
@@ -1,31 +0,0 @@
import type { BotEvent, EventCategory } from '../types'
import EventEmitter from 'eventemitter3'
export class EventManager {
private emitter = new EventEmitter()
public emit<T>(event: BotEvent<T>): void {
// TODO: Temporal Context tracking
// TODO: Salience Detection / Filtering noise
if (!event.priority) {
event.priority = 0 // Default priority
}
this.emitter.emit(event.type, event)
this.emitter.emit('*', event)
}
public on<T>(type: EventCategory | '*', handler: (event: BotEvent<T>) => void): void {
this.emitter.on(type, handler)
}
public off<T>(type: EventCategory | '*', handler: (event: BotEvent<T>) => void): void {
this.emitter.off(type, handler)
}
public once<T>(type: EventCategory | '*', handler: (event: BotEvent<T>) => void): void {
this.emitter.once(type, handler)
}
}
@@ -2,7 +2,6 @@ import type { Logg } from '@guiiai/logg'
import type { EventBus } from '../os'
import type { MineflayerWithAgents } from '../types'
import type { EventManager } from './event-manager'
import type { PerceptionFrame } from './frame'
import type { RawPerceptionEvent } from './types/raw-events'
import type { PerceptionSignal } from './types/signals'
@@ -29,7 +28,6 @@ export class PerceptionPipeline {
constructor(
private readonly deps: {
eventBus: EventBus
eventManager: EventManager
logger: Logg
},
) {
@@ -96,33 +94,6 @@ export class PerceptionPipeline {
{
name: 'router',
handle: (frame) => {
if (frame.kind === 'chat_raw') {
const raw = frame.raw as { username: string, message: string }
// Convert chat to PerceptionSignal
const signal: PerceptionSignal = {
type: 'chat_message',
description: `Chat from ${raw.username}: "${raw.message}"`,
sourceId: raw.username,
timestamp: Date.now(),
confidence: 1.0,
metadata: {
username: raw.username,
message: raw.message,
},
}
this.deps.eventManager.emit<PerceptionSignal>({
type: 'perception',
payload: signal,
source: {
type: 'minecraft',
id: raw.username,
},
timestamp: Date.now(),
})
}
// Emit all perception signals centrally as BotEvents
for (const signalWrapper of frame.signals) {
if (signalWrapper.type !== 'perception_signal')
@@ -130,11 +101,10 @@ export class PerceptionPipeline {
const signal = signalWrapper.payload as PerceptionSignal
this.deps.eventManager.emit<PerceptionSignal>({
this.deps.eventBus.emit<PerceptionSignal>({
type: 'perception',
payload: signal,
source: { type: 'minecraft', id: 'perception' },
timestamp: Date.now(),
source: { component: 'perception', id: 'perception' },
})
}