188 lines
5.9 KiB
TypeScript
188 lines
5.9 KiB
TypeScript
import type { MineflayerPlugin } from '../libs/mineflayer'
|
|
import type { CognitiveEngineOptions, MineflayerWithAgents } from './types'
|
|
|
|
import { config } from '../composables/config'
|
|
import { DebugService } from '../debug'
|
|
import { McpReplServer } from '../debug/mcp-repl-server'
|
|
import { ChatMessageHandler } from '../libs/mineflayer'
|
|
import { createAgentContainer } from './container'
|
|
|
|
export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlugin {
|
|
let container: ReturnType<typeof createAgentContainer>
|
|
let spawnHandler: (() => void) | null = null
|
|
let mcpReplServer: McpReplServer | null = null
|
|
let started = false
|
|
|
|
return {
|
|
async created(bot) {
|
|
// Create container and get required services
|
|
container = createAgentContainer(options.airiClient)
|
|
|
|
const perceptionPipeline = container.resolve('perceptionPipeline')
|
|
const brain = container.resolve('brain')
|
|
const reflexManager = container.resolve('reflexManager')
|
|
const taskExecutor = container.resolve('taskExecutor')
|
|
const airiBridge = container.resolve('airiBridge')
|
|
const minecraftContextService = container.resolve('minecraftContextService')
|
|
const debugService = DebugService.getInstance()
|
|
|
|
airiBridge.init()
|
|
minecraftContextService.init()
|
|
|
|
if (config.debug.mcp) {
|
|
mcpReplServer = new McpReplServer(brain)
|
|
mcpReplServer.start()
|
|
}
|
|
|
|
debugService.onCommand('request_repl_state', () => {
|
|
debugService.emit('debug:repl_state', brain.getReplState())
|
|
})
|
|
|
|
debugService.onCommand('request_conversation', () => {
|
|
brain.broadcastConversationState()
|
|
})
|
|
|
|
debugService.onCommand('execute_repl', async (command) => {
|
|
if (command.type !== 'execute_repl')
|
|
return
|
|
|
|
const code = command.payload?.code
|
|
if (typeof code !== 'string') {
|
|
debugService.emit('debug:repl_result', {
|
|
source: 'manual',
|
|
code: '',
|
|
logs: [],
|
|
actions: [],
|
|
error: 'Invalid REPL request: code must be a string',
|
|
durationMs: 0,
|
|
timestamp: Date.now(),
|
|
})
|
|
return
|
|
}
|
|
|
|
const result = await brain.executeDebugRepl(code)
|
|
debugService.emit('debug:repl_result', result)
|
|
})
|
|
|
|
// Initialize task executor with mineflayer instance
|
|
taskExecutor.setMineflayer(bot)
|
|
await taskExecutor.initialize()
|
|
|
|
// Type conversion
|
|
const botWithAgents = bot as unknown as MineflayerWithAgents
|
|
botWithAgents.reflexManager = reflexManager
|
|
|
|
const startCognitive = () => {
|
|
if (started)
|
|
return
|
|
started = true
|
|
|
|
// Initialize layers
|
|
reflexManager.init(botWithAgents)
|
|
brain.init(botWithAgents)
|
|
|
|
// Ensure perception rules engine is instantiated (Awilix is lazy).
|
|
void container.resolve('ruleEngine')
|
|
|
|
// Initialize perception pipeline (raw events + detectors)
|
|
perceptionPipeline.init(botWithAgents)
|
|
|
|
bot.onTick('tick', () => {
|
|
// Empty listener
|
|
})
|
|
|
|
// Resolve EventBus for message handling
|
|
const eventBus = container.resolve('eventBus')
|
|
|
|
// NOTICE: EventBus trace forwarding disabled - trace logs removed to reduce noise
|
|
// All events from EventBus were being forwarded to DebugService as trace events,
|
|
// causing thousands of 'raw:sighted:entity_moved' entries in the logs.
|
|
// Conscious layer (LLM) events are still logged separately.
|
|
|
|
// Set message handling via EventBus
|
|
const chatHandler = new ChatMessageHandler(bot.username)
|
|
bot.bot.on('chat', (username, message) => {
|
|
if (chatHandler.isBotMessage(username)) {
|
|
// The bot's own chat line — it must not react to its own messages.
|
|
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',
|
|
payload: Object.freeze({
|
|
type: 'chat_message',
|
|
description: `Chat from ${username}: "${message}"`,
|
|
sourceId: username,
|
|
confidence: 1.0,
|
|
timestamp: Date.now(),
|
|
metadata: {
|
|
username,
|
|
message,
|
|
},
|
|
}),
|
|
source: {
|
|
component: 'perception',
|
|
id: 'chat',
|
|
},
|
|
})
|
|
|
|
// Chat is handled via signal:chat_message only; no extra perception emission needed.
|
|
})
|
|
}
|
|
|
|
if (bot.bot.entity) {
|
|
startCognitive()
|
|
}
|
|
else {
|
|
spawnHandler = () => startCognitive()
|
|
bot.bot.once('spawn', spawnHandler)
|
|
}
|
|
},
|
|
|
|
async beforeCleanup(bot) {
|
|
if (mcpReplServer) {
|
|
mcpReplServer.stop()
|
|
mcpReplServer = null
|
|
}
|
|
|
|
if (container) {
|
|
const minecraftContextService = container.resolve('minecraftContextService')
|
|
minecraftContextService.destroy()
|
|
|
|
const airiBridge = container.resolve('airiBridge')
|
|
airiBridge.destroy()
|
|
|
|
const brain = container.resolve('brain')
|
|
brain.destroy()
|
|
|
|
const taskExecutor = container.resolve('taskExecutor')
|
|
await taskExecutor.destroy()
|
|
|
|
const perceptionPipeline = container.resolve('perceptionPipeline')
|
|
perceptionPipeline.destroy()
|
|
|
|
const ruleEngine = container.resolve('ruleEngine')
|
|
ruleEngine.destroy()
|
|
|
|
const reflexManager = container.resolve('reflexManager')
|
|
reflexManager.destroy()
|
|
}
|
|
|
|
if (spawnHandler) {
|
|
bot.bot.off('spawn', spawnHandler)
|
|
spawnHandler = null
|
|
}
|
|
started = false
|
|
|
|
bot.bot.removeAllListeners('chat')
|
|
},
|
|
}
|
|
}
|