feat(minecraft): event on player join, wire up eventbus
This commit is contained in:
@@ -130,30 +130,10 @@ export class Brain {
|
||||
this.debugService = DebugService.getInstance()
|
||||
}
|
||||
|
||||
public init(bot: MineflayerWithAgents): void {
|
||||
this.log('INFO', 'Brain: Initializing...')
|
||||
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
|
||||
if (signal.type !== 'chat_message')
|
||||
return
|
||||
|
||||
this.log('INFO', `Brain: Received chat: ${signal.description}`)
|
||||
|
||||
// Add to blackboard chat history
|
||||
// signal.description usually is "User: message"
|
||||
// We'll parse it simply or use the whole string as content if format varies
|
||||
// Assuming signal.description is the formatted message or we extract it.
|
||||
// Based on previous logs, it looks like "Sender: message"
|
||||
// Let's just use the description for now, or split it if possible.
|
||||
// Actually `signal.content` might hold the raw message if available, but checking types it seems signal has description and properties.
|
||||
// Let's assume description is "Sender: content" for now or just store it.
|
||||
// A better way is to try to parse it if needed, but for now we trust `signal.description`.
|
||||
private async handlePerceptionSignal(bot: MineflayerWithAgents, signal: PerceptionSignal): Promise<void> {
|
||||
this.log('INFO', `Brain: Received perception: ${signal.description}`)
|
||||
|
||||
if (signal.type === 'chat_message') {
|
||||
const parts = signal.description.split(': ')
|
||||
const sender = parts.length > 1 ? parts[0] : 'Unknown'
|
||||
const content = parts.length > 1 ? parts.slice(1).join(': ') : signal.description
|
||||
@@ -163,11 +143,33 @@ export class Brain {
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
await this.enqueueEvent(bot, {
|
||||
type: 'perception',
|
||||
payload: signal,
|
||||
source: {
|
||||
type: 'minecraft',
|
||||
id: signal.sourceId ?? 'perception',
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
|
||||
public init(bot: MineflayerWithAgents): void {
|
||||
this.log('INFO', 'Brain: Initializing...')
|
||||
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
|
||||
if (signal.type !== 'chat_message' && signal.type !== 'social_presence')
|
||||
return
|
||||
|
||||
try {
|
||||
this.log('DEBUG', `Brain: About to enqueue chat event`)
|
||||
await this.enqueueEvent(bot, event)
|
||||
this.log('DEBUG', `Brain: Chat event enqueued successfully`)
|
||||
await this.handlePerceptionSignal(bot, signal)
|
||||
}
|
||||
catch (err) {
|
||||
this.log('ERROR', `Brain: Failed to enqueue chat event`, { error: err })
|
||||
@@ -247,6 +249,9 @@ export class Brain {
|
||||
this.updateDebugState()
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
}
|
||||
|
||||
// --- Event Queue Logic ---
|
||||
|
||||
private async enqueueEvent(bot: MineflayerWithAgents, event: BotEvent): Promise<void> {
|
||||
|
||||
@@ -24,6 +24,7 @@ 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')
|
||||
@@ -47,8 +48,8 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
|
||||
reflexManager.init(botWithAgents)
|
||||
brain.init(botWithAgents)
|
||||
|
||||
const ruleEngine = container.resolve('ruleEngine')
|
||||
ruleEngine.init()
|
||||
// Ensure RuleEngine is instantiated (Awilix is lazy). It subscribes to raw:* during construction init.
|
||||
void container.resolve('ruleEngine')
|
||||
|
||||
// Initialize perception pipeline (raw events + detectors)
|
||||
perceptionPipeline.init(botWithAgents)
|
||||
@@ -72,6 +73,17 @@ 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({
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
FeltDamageTakenEvent,
|
||||
FeltItemCollectedEvent,
|
||||
HeardSoundEvent,
|
||||
PlayerJoinedEvent,
|
||||
RawPerceptionEvent,
|
||||
SightedArmSwingEvent,
|
||||
SightedEntityMovedEvent,
|
||||
@@ -19,6 +20,8 @@ export class MineflayerPerceptionCollector {
|
||||
handler: (...args: any[]) => void
|
||||
}> = []
|
||||
|
||||
private knownPlayerIds: Set<string> = new Set()
|
||||
|
||||
private lastSelfHealth: number | null = null
|
||||
private lastStatsAt = 0
|
||||
private stats: Record<string, number> = {}
|
||||
@@ -37,6 +40,7 @@ export class MineflayerPerceptionCollector {
|
||||
this.lastSelfHealth = bot.bot.health
|
||||
this.lastStatsAt = Date.now()
|
||||
this.stats = {}
|
||||
this.knownPlayerIds = this.snapshotKnownPlayers(bot)
|
||||
|
||||
this.deps.logger.withFields({ maxDistance: this.deps.maxDistance }).log('MineflayerPerceptionCollector: init')
|
||||
|
||||
@@ -73,6 +77,8 @@ export class MineflayerPerceptionCollector {
|
||||
this.onBot('entityMoved', entity => this.handleEntityMoved(entity))
|
||||
this.onBot('entitySwingArm', entity => this.handleEntitySwingArm(entity))
|
||||
this.onBot('entityUpdate', entity => this.handleEntityUpdate(entity))
|
||||
this.onBot('playerJoined', player => this.handlePlayerJoined(player))
|
||||
this.onBot('playerUpdated', () => this.handlePlayersMaybeChanged())
|
||||
this.onBot('soundEffectHeard', (soundId, pos) => this.handleSoundHeard(soundId, pos))
|
||||
this.onBot('health', () => this.handleHealthChange())
|
||||
this.onBot('playerCollect', (collector, collected) => this.handleItemCollected(collector, collected))
|
||||
@@ -230,6 +236,82 @@ export class MineflayerPerceptionCollector {
|
||||
this.emitEvent(event, 'felt.item_collected')
|
||||
}
|
||||
|
||||
private handlePlayerJoined(player: any): void {
|
||||
if (!player)
|
||||
return
|
||||
|
||||
if (player.username === this.bot?.bot.username)
|
||||
return
|
||||
|
||||
const playerId = String(player.uuid ?? player.id ?? player.username ?? 'unknown')
|
||||
if (this.knownPlayerIds.has(playerId))
|
||||
return
|
||||
|
||||
this.knownPlayerIds.add(playerId)
|
||||
|
||||
const event: PlayerJoinedEvent = {
|
||||
modality: 'system',
|
||||
kind: 'player_joined',
|
||||
playerId,
|
||||
displayName: player.username,
|
||||
timestamp: Date.now(),
|
||||
source: 'minecraft',
|
||||
}
|
||||
|
||||
this.emitEvent(event, 'system.player_joined')
|
||||
}
|
||||
|
||||
private handlePlayersMaybeChanged(): void {
|
||||
const bot = this.bot
|
||||
if (!bot)
|
||||
return
|
||||
|
||||
const current = this.snapshotKnownPlayers(bot)
|
||||
|
||||
for (const playerId of current) {
|
||||
if (this.knownPlayerIds.has(playerId))
|
||||
continue
|
||||
|
||||
this.knownPlayerIds.add(playerId)
|
||||
|
||||
const player = (bot.bot as any).players?.[playerId]
|
||||
const username = player?.username
|
||||
|
||||
const event: PlayerJoinedEvent = {
|
||||
modality: 'system',
|
||||
kind: 'player_joined',
|
||||
playerId,
|
||||
displayName: typeof username === 'string' ? username : undefined,
|
||||
timestamp: Date.now(),
|
||||
source: 'minecraft',
|
||||
}
|
||||
|
||||
this.emitEvent(event, 'system.player_joined')
|
||||
}
|
||||
}
|
||||
|
||||
private snapshotKnownPlayers(bot: MineflayerWithAgents): Set<string> {
|
||||
const out = new Set<string>()
|
||||
const players = (bot.bot as any).players as Record<string, any> | undefined
|
||||
if (!players)
|
||||
return out
|
||||
|
||||
const selfUsername = bot.bot.username
|
||||
|
||||
for (const [id, player] of Object.entries(players)) {
|
||||
if (!id)
|
||||
continue
|
||||
|
||||
const username = player?.username
|
||||
if (username && username === selfUsername)
|
||||
continue
|
||||
|
||||
out.add(String(id))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Validation Helpers
|
||||
// ========================================
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Vec3 } from 'vec3'
|
||||
|
||||
export type PerceptionModality = 'sighted' | 'heard' | 'felt'
|
||||
export type PerceptionModality = 'sighted' | 'heard' | 'felt' | 'system'
|
||||
|
||||
export interface RawPerceptionEventBase {
|
||||
modality: PerceptionModality
|
||||
@@ -71,4 +71,13 @@ export interface FeltItemCollectedEvent extends RawPerceptionEventBase {
|
||||
|
||||
export type FeltEvent = FeltDamageTakenEvent | FeltItemCollectedEvent
|
||||
|
||||
export type RawPerceptionEvent = SightedEvent | HeardEvent | FeltEvent
|
||||
export interface PlayerJoinedEvent extends RawPerceptionEventBase {
|
||||
modality: 'system'
|
||||
kind: 'player_joined'
|
||||
playerId: string
|
||||
displayName?: string
|
||||
}
|
||||
|
||||
export type SystemEvent = PlayerJoinedEvent
|
||||
|
||||
export type RawPerceptionEvent = SightedEvent | HeardEvent | FeltEvent | SystemEvent
|
||||
|
||||
@@ -4,6 +4,7 @@ export type PerceptionSignalType
|
||||
| 'environmental_anomaly' // e.g. sudden loud sound
|
||||
| 'saliency_high' // generic high saliency event
|
||||
| 'social_gesture' // e.g. teabagging, waving
|
||||
| 'social_presence'
|
||||
|
||||
export interface PerceptionSignal {
|
||||
type: PerceptionSignalType
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: player-joined
|
||||
version: 1
|
||||
|
||||
trigger:
|
||||
modality: system
|
||||
kind: player_joined
|
||||
|
||||
accumulator:
|
||||
threshold: 1
|
||||
window: 1s
|
||||
|
||||
signal:
|
||||
type: social_presence
|
||||
description: 'Player {{ displayName }} joined the server'
|
||||
confidence: 1.0
|
||||
metadata:
|
||||
event: player_joined
|
||||
playerId: '{{ playerId }}'
|
||||
displayName: '{{ displayName }}'
|
||||
Reference in New Issue
Block a user