diff --git a/services/minecraft/src/cognitive/action/action-registry.ts b/services/minecraft/src/cognitive/action/action-registry.ts index 265e4cbb9..fd1b5168d 100644 --- a/services/minecraft/src/cognitive/action/action-registry.ts +++ b/services/minecraft/src/cognitive/action/action-registry.ts @@ -42,20 +42,15 @@ export class ActionRegistry { throw new Error(`Unknown action: ${step.tool}`) } - try { - const actionFn = action.perform(this.mineflayer) - const { schema } = action - const parsedParams = schema.parse(step.params || {}) + const actionFn = action.perform(this.mineflayer) + const { schema } = action + const parsedParams = schema.parse(step.params || {}) - // Extract parameter values in the order defined by the schema - const paramValues = Object.keys((schema as any).shape || {}).map(key => parsedParams[key]) + // Extract parameter values in the order defined by the schema + const paramValues = Object.keys((schema as any).shape || {}).map(key => parsedParams[key]) - const result = await actionFn(...paramValues) - return result ?? `Action ${step.tool} completed` - } - catch (error) { - throw error - } + const result = await actionFn(...paramValues) + return result ?? `Action ${step.tool} completed` } /** diff --git a/services/minecraft/src/cognitive/action/llm-actions.test.ts b/services/minecraft/src/cognitive/action/llm-actions.test.ts index acef9ab27..a92b1299b 100644 --- a/services/minecraft/src/cognitive/action/llm-actions.test.ts +++ b/services/minecraft/src/cognitive/action/llm-actions.test.ts @@ -48,4 +48,12 @@ describe('llm-actions mineBlockAt', () => { await expect(perform(1, 2, 3, 'torch')).rejects.toThrow(/Block type mismatch/i) expect(breakBlockAt).not.toHaveBeenCalled() }) + + it('exposes skip tool with stable return value', async () => { + const skipAction = actionsList.find(item => item.name === 'skip') + expect(skipAction).toBeDefined() + + const perform = skipAction!.perform({} as any) + expect(perform()).toBe('Skipped turn') + }) }) diff --git a/services/minecraft/src/cognitive/action/llm-actions.ts b/services/minecraft/src/cognitive/action/llm-actions.ts index 6105fecfb..df5e1a9cf 100644 --- a/services/minecraft/src/cognitive/action/llm-actions.ts +++ b/services/minecraft/src/cognitive/action/llm-actions.ts @@ -47,23 +47,13 @@ export const actionsList: Action[] = [ }), perform: () => (reason: string, cooldown_seconds: number): string => `Gave up for ${cooldown_seconds}s: ${reason}`, }, - // {\n // 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: 'skip', + description: 'Skip this turn without performing any world action.', + execution: 'sync', + schema: z.object({}), + perform: () => (): string => 'Skipped turn', + }, { name: 'stop', description: 'Force stop all actions', // TODO: include name of the current action in description? @@ -177,17 +167,6 @@ export const actionsList: Action[] = [ } }, }, - // { - // name: 'moveAway', - // description: 'Move away from the current location in any direction by a given distance.', - // schema: z.object({ - // distance: z.number().describe('The distance to move away.').min(0), - // }), - // perform: mineflayer => async (distance: number) => { - // await skills.moveAway(mineflayer, distance) - // return 'Moved away' - // }, - // }, { name: 'givePlayer', description: 'Give the specified item to the given player.', @@ -252,15 +231,6 @@ export const actionsList: Action[] = [ return `Took [${item_name}]x${num} from chest` }, }, - // { - // name: 'viewChest', - // description: 'View the items/counts of the nearest chest.', - // schema: z.object({}), - // perform: mineflayer => async () => { - // await viewChest(mineflayer) - // return 'Viewed chest contents' - // }, - // }, { name: 'discard', description: 'Discard the given item from the inventory.', diff --git a/services/minecraft/src/cognitive/action/task-executor.ts b/services/minecraft/src/cognitive/action/task-executor.ts index f6dbe5b8b..4883152f5 100644 --- a/services/minecraft/src/cognitive/action/task-executor.ts +++ b/services/minecraft/src/cognitive/action/task-executor.ts @@ -16,7 +16,6 @@ export class TaskExecutor extends EventEmitter { private logger: Logger private initialized = false private actionRegistry: ActionRegistry - private mineflayer: Mineflayer | null = null constructor(config: TaskExecutorConfig) { super() @@ -36,7 +35,6 @@ export class TaskExecutor extends EventEmitter { * Set the mineflayer instance for action execution */ public setMineflayer(mineflayer: Mineflayer): void { - this.mineflayer = mineflayer this.actionRegistry.setMineflayer(mineflayer) } @@ -70,34 +68,12 @@ export class TaskExecutor extends EventEmitter { this.emit('action:started', { action }) try { - let result: unknown - - if (action.tool === 'chat') { - // Handle chat action via mineflayer directly - const message = action.params.message - if (typeof message !== 'string' || message.trim().length === 0) - throw new Error('Invalid chat tool params: expected params.message to be a string') - - if (!this.mineflayer) { - throw new Error('Mineflayer instance not set in TaskExecutor') - } - - this.mineflayer.bot.chat(message) - result = `Sent message: "${message}"` - } - else if (action.tool === 'skip') { - result = 'Skipped turn' - } - else { - // Dispatch to ActionRegistry - const step = { - description: action.tool, - tool: action.tool, - params: action.params, - } - - result = await this.actionRegistry.performAction(step) + const step = { + description: action.tool, + tool: action.tool, + params: action.params, } + const result = await this.actionRegistry.performAction(step) this.emit('action:completed', { action, result }) return result diff --git a/services/minecraft/src/cognitive/action/types/index.ts b/services/minecraft/src/cognitive/action/types/index.ts index 98851e631..a92057c05 100644 --- a/services/minecraft/src/cognitive/action/types/index.ts +++ b/services/minecraft/src/cognitive/action/types/index.ts @@ -7,14 +7,6 @@ export interface ActionInstruction { params: Record } -/** - * LLM response format for the stateful agent. - * Legacy JSON response format retained for backward compatibility. - */ -export interface LLMResponse { - action: ActionInstruction -} - /** * PlanStep for action planning - compatible with ActionInstruction */ diff --git a/services/minecraft/src/cognitive/conscious/js-planner.test.ts b/services/minecraft/src/cognitive/conscious/js-planner.test.ts index 2c5a163a9..37ff73b10 100644 --- a/services/minecraft/src/cognitive/conscious/js-planner.test.ts +++ b/services/minecraft/src/cognitive/conscious/js-planner.test.ts @@ -23,6 +23,11 @@ const actions: Action[] = [ })), ] +const actionsWithSkip: Action[] = [ + createAction('skip', z.object({})), + ...actions, +] + describe('javaScriptPlanner', () => { const globals = { event: { @@ -192,6 +197,21 @@ describe('javaScriptPlanner', () => { await expect(planner.evaluate('await skip(); await chat("oops")', actions, globals, executeAction)).rejects.toThrow(/skip\(\) cannot be mixed/i) }) + it('allows evaluate when action catalog also includes skip', async () => { + const planner = new JavaScriptPlanner() + const executeAction = vi.fn(async action => `ok:${action.tool}`) + + await expect(planner.evaluate('await skip()', actionsWithSkip, globals, executeAction)).resolves.toMatchObject({ + actions: [ + { + action: { tool: 'skip', params: {} }, + ok: true, + result: 'Skipped turn', + }, + ], + }) + }) + it('returns structured validation failures without aborting the script', async () => { const planner = new JavaScriptPlanner() const executeAction = vi.fn(async action => `ok:${action.tool}`) diff --git a/services/minecraft/src/cognitive/conscious/js-planner.ts b/services/minecraft/src/cognitive/conscious/js-planner.ts index 8284ec049..2f4df688d 100644 --- a/services/minecraft/src/cognitive/conscious/js-planner.ts +++ b/services/minecraft/src/cognitive/conscious/js-planner.ts @@ -417,6 +417,10 @@ export class JavaScriptPlanner { private installActionTools(availableActions: Action[]): void { for (const action of availableActions) { + const existing = Object.getOwnPropertyDescriptor(this.sandbox, action.name) + if (existing && existing.configurable === false) + continue + this.defineUpdatableGlobal(action.name, async (...args: unknown[]) => { const params = this.mapArgsToParams(action, args) return this.runAction(action.name, params) diff --git a/services/minecraft/src/cognitive/conscious/task-manager.ts b/services/minecraft/src/cognitive/conscious/task-manager.ts deleted file mode 100644 index 564acc680..000000000 --- a/services/minecraft/src/cognitive/conscious/task-manager.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { Logg } from '@guiiai/logg' - -import type { Plan } from '../../libs/mineflayer/base-agent' -import type { TaskContext, TaskStatus } from './task-state' - -import { createCancellationToken } from './task-state' - -export class TaskManager { - private primaryTask: TaskContext | null = null - private secondaryTasks: Map = new Map() - private taskHistory: TaskContext[] = [] - private readonly maxHistorySize = 10 - - constructor(private readonly logger: Logg) {} - - /** - * Create a new task. - * If strictlySecondary is true, it will always be created as specific type (e.g. for forced background tasks). - * Otherwise, if no primary task exists, new task becomes primary. - */ - public createTask(goal: string): TaskContext { - const task: TaskContext = { - id: this.generateTaskId(), - goal, - status: 'idle', - startTime: Date.now(), - cancellationToken: createCancellationToken(), - } - - if (!this.primaryTask) { - this.primaryTask = task - this.logger.withFields({ taskId: task.id, goal, type: 'primary' }).log('TaskManager: Created new primary task') - } - else { - this.secondaryTasks.set(task.id, task) - this.logger.withFields({ taskId: task.id, goal, type: 'secondary' }).log('TaskManager: Created new secondary task') - } - - return task - } - - /** - * Update the status of a specific task - */ - public updateTaskStatus(taskId: string, status: TaskStatus, currentStep?: string): void { - const task = this.getTaskById(taskId) - if (!task) { - this.logger.warn(`TaskManager: Task ${taskId} not found for update`) - return - } - - task.status = status - if (currentStep) { - task.currentStep = currentStep - } - - this.logger.withFields({ - taskId: task.id, - status, - currentStep, - }).log('TaskManager: Updated task status') - } - - /** - * Set the plan for a specific task - */ - public setTaskPlan(taskId: string, plan: Plan): void { - const task = this.getTaskById(taskId) - if (!task) { - this.logger.warn(`TaskManager: Task ${taskId} not found to set plan`) - return - } - - task.plan = plan - this.logger.withFields({ taskId: task.id }).log('TaskManager: Set task plan') - } - - /** - * Cancel a specific task. If no taskId provided, cancels primary task. - */ - public cancelTask(taskId: string, reason?: string): void { - const task = this.getTaskById(taskId) - if (!task) { - this.logger.warn(`TaskManager: Task ${taskId} not found to cancel`) - return - } - - this.logger.withFields({ - taskId: task.id, - reason, - }).log('TaskManager: Cancelling task') - - task.status = 'cancelling' - task.cancellationToken.cancel() - - // We don't remove it yet, we wait for completeTask to be called - this.addToHistory(task) - - // Cleanup reference immediately to allow new primary tasks if this was primary? - // No, we should wait for the orchestrator to call completeTask/cleanup. - } - - /** - * Cancel currently active primary task - */ - public cancelPrimaryTask(reason?: string): void { - if (this.primaryTask) { - this.cancelTask(this.primaryTask.id, reason) - } - } - - /** - * Complete a task and remove it from active list - */ - public completeTask(taskId: string): void { - const task = this.getTaskById(taskId) - if (!task) - return - - this.logger.withFields({ taskId: task.id }).log('TaskManager: Task completed') - this.addToHistory(task) - - if (this.primaryTask?.id === taskId) { - this.primaryTask = null - } - else { - this.secondaryTasks.delete(taskId) - } - } - - /** - * Get the current primary task - */ - public getPrimaryTask(): TaskContext | null { - return this.primaryTask - } - - /** - * Check if there is a primary task running - */ - public hasPrimaryTask(): boolean { - return this.primaryTask !== null - } - - /** - * Get a task by ID - */ - public getTaskById(taskId: string): TaskContext | null { - if (this.primaryTask?.id === taskId) - return this.primaryTask - return this.secondaryTasks.get(taskId) || null - } - - /** - * Get formatted task context for LLM. - * Includes Primary Task and summary of Secondary Tasks. - */ - public getTaskContextForLLM(): string { - const lines: string[] = [] - - // Primary Task - if (this.primaryTask) { - const { goal, status, startTime, currentStep, plan } = this.primaryTask - const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000) - lines.push(`[PRIMARY TASK] (${status.toUpperCase()}): "${goal}"`) - lines.push(`- Duration: ${elapsedSeconds}s`) - if (currentStep) - lines.push(`- Step: ${currentStep}`) - if (plan) - lines.push(`- Plan: ${plan.steps.length} steps (${plan.status})`) - } - else { - lines.push('No primary task active.') - } - - // Secondary Tasks - if (this.secondaryTasks.size > 0) { - lines.push('\n[SECONDARY TASKS]') - for (const task of this.secondaryTasks.values()) { - lines.push(`- [${task.status.toUpperCase()}] "${task.goal}"`) - } - } - - return lines.join('\n') - } - - /** - * Get all active tasks for debugging - */ - public getAllActiveTasks(): TaskContext[] { - const tasks: TaskContext[] = [] - if (this.primaryTask) - tasks.push(this.primaryTask) - tasks.push(...this.secondaryTasks.values()) - return tasks - } - - /** - * Get task history - */ - public getTaskHistory(): TaskContext[] { - return [...this.taskHistory] - } - - private generateTaskId(): string { - return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` - } - - private addToHistory(task: TaskContext): void { - // Only add if not already in history (simple check) - if (this.taskHistory.some(t => t.id === task.id)) - return - - this.taskHistory.push(task) - if (this.taskHistory.length > this.maxHistorySize) { - this.taskHistory.shift() - } - } -} diff --git a/services/minecraft/src/cognitive/container.ts b/services/minecraft/src/cognitive/container.ts index b81767693..6bee63d64 100644 --- a/services/minecraft/src/cognitive/container.ts +++ b/services/minecraft/src/cognitive/container.ts @@ -84,10 +84,9 @@ export function createAgentContainer() { })), // Reflex Manager (Reactive Layer) - reflexManager: asFunction(({ eventBus, perceptionPipeline, taskExecutor, logger }) => + reflexManager: asFunction(({ eventBus, taskExecutor, logger }) => new ReflexManager({ eventBus, - perception: perceptionPipeline.getPerceptionAPI(), taskExecutor, logger, }), diff --git a/services/minecraft/src/cognitive/perception/events/definitions/index.ts b/services/minecraft/src/cognitive/perception/events/definitions/index.ts index 264fdfa1d..be03277e6 100644 --- a/services/minecraft/src/cognitive/perception/events/definitions/index.ts +++ b/services/minecraft/src/cognitive/perception/events/definitions/index.ts @@ -1,9 +1,7 @@ import { armSwingEvent } from './arm-swing' import { damageTakenEvent } from './damage-taken' import { entityMovedEvent } from './entity-moved' -import { itemCollectedEvent } from './item-collected' import { sneakToggleEvent } from './sneak-toggle' -import { soundHeardEvent } from './sound-heard' import { systemMessageEvent } from './system-message' export const allEventDefinitions = [ @@ -11,17 +9,13 @@ export const allEventDefinitions = [ armSwingEvent, sneakToggleEvent, entityMovedEvent, - soundHeardEvent, damageTakenEvent, - itemCollectedEvent, ] export { armSwingEvent, damageTakenEvent, entityMovedEvent, - itemCollectedEvent, sneakToggleEvent, - soundHeardEvent, systemMessageEvent, } diff --git a/services/minecraft/src/cognitive/perception/events/definitions/item-collected.ts b/services/minecraft/src/cognitive/perception/events/definitions/item-collected.ts deleted file mode 100644 index 2ada07638..000000000 --- a/services/minecraft/src/cognitive/perception/events/definitions/item-collected.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { definePerceptionEvent } from '..' - -interface ItemCollectedExtract { - itemName: string -} - -export const itemCollectedEvent = definePerceptionEvent<[any, any], ItemCollectedExtract>({ - id: 'item_collected', - modality: 'felt', - kind: 'item_collected', - - mineflayer: { - event: 'playerCollect', - filter: (ctx, collector, _collected) => { - if (!collector) - return false - return collector.username === ctx.selfUsername - }, - extract: (_ctx, _collector, collected) => ({ - itemName: String(collected?.name ?? collected?.displayName ?? collected?.type ?? 'unknown'), - }), - }, - -}) diff --git a/services/minecraft/src/cognitive/perception/events/definitions/sound-heard.ts b/services/minecraft/src/cognitive/perception/events/definitions/sound-heard.ts deleted file mode 100644 index eb546bee9..000000000 --- a/services/minecraft/src/cognitive/perception/events/definitions/sound-heard.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Vec3 } from 'vec3' - -import { definePerceptionEvent } from '..' - -interface SoundHeardExtract { - soundId: string - distance: number - pos: Vec3 -} - -export const soundHeardEvent = definePerceptionEvent<[string, Vec3], SoundHeardExtract>({ - id: 'sound_heard', - modality: 'heard', - kind: 'sound', - - mineflayer: { - event: 'soundEffectHeard', - filter: (ctx, _soundId, pos) => { - if (!pos) - return false - const dist = ctx.distanceToPos(pos) - return dist !== null && dist <= ctx.maxDistance - }, - extract: (ctx, soundId, pos) => ({ - soundId, - distance: ctx.distanceToPos(pos)!, - pos, - }), - }, - -}) diff --git a/services/minecraft/src/cognitive/perception/perception-api.ts b/services/minecraft/src/cognitive/perception/perception-api.ts deleted file mode 100644 index ce5bb0aaa..000000000 --- a/services/minecraft/src/cognitive/perception/perception-api.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { Logg } from '@guiiai/logg' - -import type { Belief, EntityState, EntityView } from '../world/types' - -import { BeliefEngine } from '../semantics/belief-engine' -import { teabagPattern } from '../semantics/patterns/teabag' -import { EntityStore } from '../world/entity-store' -import { TemporalBuffer } from '../world/temporal-buffer' - -/** - * Unified perception API for upper layers - * Provides entity queries and belief computations - */ -export class PerceptionAPI { - private store: EntityStore - private buffer: TemporalBuffer - private engine: BeliefEngine - - constructor( - private readonly deps: { - logger: Logg - }, - ) { - this.deps.logger.log('PerceptionAPI: initialized') - this.store = new EntityStore() - this.buffer = new TemporalBuffer(5000) // 5s history - this.engine = new BeliefEngine() - - // Register default patterns - this.engine.register(teabagPattern) - } - - // ============ Entity Updates (from Mineflayer) ============ - - /** - * Update an entity's state (called by perception collector) - */ - updateEntity(id: string, partial: Partial): void { - const changes = this.store.update(id, partial) - this.buffer.recordAll(changes) - } - - /** - * Remove an entity - */ - removeEntity(id: string): void { - this.store.remove(id) - this.buffer.clearEntity(id) - } - - /** - * Update self position (for distance calculations) - */ - updateSelfPosition(x: number, y: number, z: number): void { - this.store.updateSelfPosition({ x, y, z } as any) - } - - // ============ Entity Queries ============ - - /** - * Get all player entities with computed beliefs - */ - getPlayers(): EntityView[] { - return this.store.getPlayers().map(e => this.buildEntityView(e)) - } - - /** - * Get a specific entity by ID - */ - getEntity(id: string): EntityView | null { - const state = this.store.get(id) - if (!state) - return null - return this.buildEntityView(state) - } - - // ============ Belief Queries ============ - - /** - * Find entities with high confidence of a pattern - */ - entitiesWithBelief(pattern: string, minConfidence: number = 0.5): EntityView[] { - return this.getPlayers().filter(e => (e.beliefs[pattern]?.confidence ?? 0) >= minConfidence) - } - - /** - * Get the top belief for an entity - */ - getTopBelief(entityId: string): { pattern: string, belief: Belief } | null { - const entity = this.getEntity(entityId) - if (!entity) - return null - - let top: { pattern: string, belief: Belief } | null = null - for (const [pattern, belief] of Object.entries(entity.beliefs)) { - if (!top || belief.confidence > top.belief.confidence) { - top = { pattern, belief } - } - } - return top - } - - // ============ Pattern Management ============ - - /** - * Register a custom pattern - */ - registerPattern(pattern: Parameters[0]): void { - this.engine.register(pattern) - } - - // ============ Maintenance ============ - - /** - * Prune old history entries - */ - prune(): void { - this.buffer.prune() - } - - /** - * Clear all state - */ - clear(): void { - this.store.clear() - this.buffer.clear() - } - - // ============ Internal ============ - - private buildEntityView(state: EntityState): EntityView { - const beliefs = this.engine.computeBeliefs( - state.id, - id => this.store.get(id), - (id, since) => this.buffer.query(id, since), - this.store.getSelfPosition(), - ) - - return { - id: state.id, - name: state.name ?? state.id, - type: state.type, - state, - beliefs, - distanceToSelf: this.store.distanceToSelf(state.id) ?? Infinity, - } - } -} diff --git a/services/minecraft/src/cognitive/perception/pipeline.ts b/services/minecraft/src/cognitive/perception/pipeline.ts index d885e90f8..10f8cecf0 100644 --- a/services/minecraft/src/cognitive/perception/pipeline.ts +++ b/services/minecraft/src/cognitive/perception/pipeline.ts @@ -5,10 +5,8 @@ import type { MineflayerWithAgents } from '../types' import { EventRegistry } from './events' import { allEventDefinitions } from './events/definitions' -import { PerceptionAPI } from './perception-api' export class PerceptionPipeline { - private readonly perception: PerceptionAPI private readonly eventRegistry: EventRegistry private bot: MineflayerWithAgents | null = null @@ -18,8 +16,6 @@ export class PerceptionPipeline { logger: Logg }, ) { - this.perception = new PerceptionAPI({ logger: this.deps.logger }) - this.eventRegistry = new EventRegistry({ logger: this.deps.logger, onRawEvent: (event) => { @@ -51,11 +47,4 @@ export class PerceptionPipeline { this.eventRegistry.stop() this.bot = null } - - /** - * Get the PerceptionAPI for querying entity beliefs - */ - public getPerceptionAPI(): PerceptionAPI { - return this.perception - } } diff --git a/services/minecraft/src/cognitive/perception/rules/social/player-joined.yaml b/services/minecraft/src/cognitive/perception/rules/social/player-joined.yaml deleted file mode 100644 index ae7658cdc..000000000 --- a/services/minecraft/src/cognitive/perception/rules/social/player-joined.yaml +++ /dev/null @@ -1,19 +0,0 @@ -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 }}' diff --git a/services/minecraft/src/cognitive/reflex/behavior.ts b/services/minecraft/src/cognitive/reflex/behavior.ts deleted file mode 100644 index f622a4738..000000000 --- a/services/minecraft/src/cognitive/reflex/behavior.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './types/behavior' diff --git a/services/minecraft/src/cognitive/reflex/reflex-manager.ts b/services/minecraft/src/cognitive/reflex/reflex-manager.ts index 41ff6f158..c1cc94bf7 100644 --- a/services/minecraft/src/cognitive/reflex/reflex-manager.ts +++ b/services/minecraft/src/cognitive/reflex/reflex-manager.ts @@ -2,7 +2,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' import type { MineflayerWithAgents } from '../types' import type { ReflexContextState } from './context' @@ -21,7 +20,6 @@ export class ReflexManager { constructor( private readonly deps: { eventBus: EventBus - perception: PerceptionAPI taskExecutor: TaskExecutor logger: Logg }, @@ -111,7 +109,7 @@ export class ReflexManager { if (!this.bot) return - this.runtime.tick(this.bot, 0, this.deps.perception) + this.runtime.tick(this.bot, 0) this.emitReflexState() } @@ -159,7 +157,7 @@ export class ReflexManager { // Assuming 'signal:social:chat' or similar might exist later. // Trigger behavior selection - this.runtime.tick(bot, 0, this.deps.perception) + this.runtime.tick(bot, 0) // Forward signals to conscious layer (Brain) ONLY when Reflex decides. if (this.shouldForwardToConscious(signal)) { diff --git a/services/minecraft/src/cognitive/reflex/runtime.ts b/services/minecraft/src/cognitive/reflex/runtime.ts index 59599c9af..46a1421e0 100644 --- a/services/minecraft/src/cognitive/reflex/runtime.ts +++ b/services/minecraft/src/cognitive/reflex/runtime.ts @@ -1,6 +1,5 @@ import type { Logg } from '@guiiai/logg' -import type { PerceptionAPI } from '../perception/perception-api' import type { MineflayerWithAgents } from '../types' import type { ReflexModeId } from './modes' import type { ReflexBehavior } from './types/behavior' @@ -90,7 +89,7 @@ export class ReflexRuntime { this.behaviors.push(behavior) } - public tick(bot: MineflayerWithAgents, deltaMs: number, perception: PerceptionAPI): string | null { + public tick(bot: MineflayerWithAgents, deltaMs: number): string | null { const now = Date.now() this.context.updateNow(now) @@ -167,7 +166,7 @@ export class ReflexRuntime { this.activeBehaviorUntil = null const ctx = this.context.getSnapshot() - const api = { bot, context: this.context, perception } + const api = { bot, context: this.context } let best: { behavior: ReflexBehavior, score: number } | null = null for (const behavior of this.behaviors) { diff --git a/services/minecraft/src/cognitive/reflex/types/behavior.ts b/services/minecraft/src/cognitive/reflex/types/behavior.ts index 5032586c9..a19b85c3a 100644 --- a/services/minecraft/src/cognitive/reflex/types/behavior.ts +++ b/services/minecraft/src/cognitive/reflex/types/behavior.ts @@ -1,4 +1,3 @@ -import type { PerceptionAPI } from '../../perception/perception-api' import type { MineflayerWithAgents } from '../../types' import type { ReflexContext } from '../context' import type { ReflexModeId } from '../modes' @@ -6,7 +5,6 @@ import type { ReflexModeId } from '../modes' export interface ReflexApi { bot: MineflayerWithAgents context: ReflexContext - perception: PerceptionAPI } export interface ReflexBehavior { diff --git a/services/minecraft/src/cognitive/semantics/belief-engine.ts b/services/minecraft/src/cognitive/semantics/belief-engine.ts deleted file mode 100644 index 0b4c0252a..000000000 --- a/services/minecraft/src/cognitive/semantics/belief-engine.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Vec3 } from 'vec3' - -import type { Belief, EntityState, PatternDefinition, StateChange } from '../world/types' - -/** - * Computes beliefs about entity behaviors based on registered patterns - */ -export class BeliefEngine { - private patterns: Map = new Map() - - /** - * Register a pattern - */ - register(pattern: PatternDefinition): void { - this.patterns.set(pattern.id, pattern) - } - - /** - * Unregister a pattern - */ - unregister(id: string): void { - this.patterns.delete(id) - } - - /** - * Get all registered pattern IDs - */ - getPatternIds(): string[] { - return Array.from(this.patterns.keys()) - } - - /** - * Compute all beliefs for an entity - */ - computeBeliefs( - entityId: string, - getState: (id: string) => EntityState | null, - getHistory: (id: string, since: number) => StateChange[], - selfPosition: Vec3 | null, - ): Record { - const beliefs: Record = {} - - for (const [id, pattern] of this.patterns) { - try { - beliefs[id] = pattern.compute(entityId, getState, getHistory, selfPosition) - } - catch { - // Pattern threw an error, treat as no belief - beliefs[id] = { confidence: 0 } - } - } - - return beliefs - } - - /** - * Compute a single belief for an entity - */ - computeBelief( - patternId: string, - entityId: string, - getState: (id: string) => EntityState | null, - getHistory: (id: string, since: number) => StateChange[], - selfPosition: Vec3 | null, - ): Belief | null { - const pattern = this.patterns.get(patternId) - if (!pattern) - return null - - try { - return pattern.compute(entityId, getState, getHistory, selfPosition) - } - catch { - return { confidence: 0 } - } - } -} diff --git a/services/minecraft/src/cognitive/semantics/index.ts b/services/minecraft/src/cognitive/semantics/index.ts deleted file mode 100644 index f4f074b30..000000000 --- a/services/minecraft/src/cognitive/semantics/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BeliefEngine } from './belief-engine' -export { teabagPattern } from './patterns/teabag' diff --git a/services/minecraft/src/cognitive/semantics/patterns/teabag.ts b/services/minecraft/src/cognitive/semantics/patterns/teabag.ts deleted file mode 100644 index d4052e6b0..000000000 --- a/services/minecraft/src/cognitive/semantics/patterns/teabag.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { PatternDefinition } from '../../world/types' - -/** - * Teabag pattern: detects rapid crouching behavior - */ -export const teabagPattern: PatternDefinition = { - id: 'teabag', - category: 'social', - description: 'Rapid crouching, typically a greeting or taunt', - - compute(entityId, getState, getHistory, selfPosition) { - // Get sneaking state changes in the last 2 seconds - const since = Date.now() - 2000 - const changes = getHistory(entityId, since).filter(c => c.field === 'isSneaking') - - // Need at least 4 toggles (2 full crouch cycles) - if (changes.length < 4) { - return { confidence: 0 } - } - - // Check distance if we have self position - const entity = getState(entityId) - if (entity && selfPosition) { - const dx = entity.position.x - selfPosition.x - const dy = entity.position.y - selfPosition.y - const dz = entity.position.z - selfPosition.z - const distance = Math.sqrt(dx * dx + dy * dy + dz * dz) - - // Too far away, reduce confidence - if (distance > 15) { - return { confidence: 0, data: { toggles: changes.length, distance } } - } - } - - // Calculate frequency (toggles per second) - const duration = (changes[changes.length - 1].timestamp - changes[0].timestamp) / 1000 - const frequency = duration > 0 ? changes.length / duration : 0 - - // Confidence based on: - // - Number of toggles (more = more confident, up to 8) - // - Frequency (faster = more confident, up to 4 Hz) - const countFactor = Math.min(1, changes.length / 8) - const frequencyFactor = Math.min(1, frequency / 4) - - const confidence = (countFactor * 0.4) + (frequencyFactor * 0.6) - - return { - confidence, - data: { - toggles: changes.length, - frequency: Math.round(frequency * 100) / 100, - duration: Math.round(duration * 1000), - }, - } - }, -} diff --git a/services/minecraft/src/cognitive/types/index.ts b/services/minecraft/src/cognitive/types/index.ts index 4c4d5ce0b..aca320cc4 100644 --- a/services/minecraft/src/cognitive/types/index.ts +++ b/services/minecraft/src/cognitive/types/index.ts @@ -3,11 +3,6 @@ import type { Client } from '@proj-airi/server-sdk' import type { Mineflayer } from '../../libs/mineflayer' import type { ReflexManager } from '../reflex/reflex-manager' -export interface LLMResponse { - content: string - usage?: any -} - export interface MineflayerWithAgents extends Mineflayer { reflexManager: ReflexManager } @@ -17,7 +12,7 @@ export interface CognitiveEngineOptions { } // TODO: currently stimulus is just chat events, consider renaming to 'input' or 'user_interaction' -export type EventCategory = 'perception' | 'feedback' | 'world_update' | 'system_alert' +export type EventCategory = 'perception' | 'feedback' | 'system_alert' export interface BotEventSource { type: 'minecraft' | 'airi' | 'system' @@ -34,8 +29,3 @@ export interface BotEvent { priority?: number // Higher is more urgent handled?: boolean // Set by Reflex layer to inhibit Conscious layer } - -export interface WorldUpdatePayload { - event: string - data: any -} diff --git a/services/minecraft/src/cognitive/world/entity-store.ts b/services/minecraft/src/cognitive/world/entity-store.ts deleted file mode 100644 index 332511ddf..000000000 --- a/services/minecraft/src/cognitive/world/entity-store.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { Vec3 } from 'vec3' - -import type { EntityState, StateChange } from './types' - -/** - * Tracks the state of all known entities - */ -export class EntityStore { - private entities: Map = new Map() - private selfPosition: Vec3 | null = null - - /** - * Update or create an entity's state - * Returns list of state changes that occurred - */ - update(id: string, partial: Partial): StateChange[] { - const now = Date.now() - const changes: StateChange[] = [] - const existing = this.entities.get(id) - - if (!existing) { - // New entity - const newState: EntityState = { - id, - type: partial.type ?? 'player', - name: partial.name, - position: partial.position ?? { x: 0, y: 0, z: 0 } as Vec3, - velocity: partial.velocity ?? { x: 0, y: 0, z: 0 } as Vec3, - yaw: partial.yaw ?? 0, - pitch: partial.pitch ?? 0, - isSneaking: partial.isSneaking ?? false, - isSprinting: partial.isSprinting ?? false, - onGround: partial.onGround ?? true, - firstSeen: now, - lastUpdate: now, - } - this.entities.set(id, newState) - return changes - } - - // Track changes to relevant fields - const trackedFields: (keyof EntityState)[] = ['isSneaking', 'isSprinting', 'onGround'] - - for (const field of trackedFields) { - if (field in partial && partial[field] !== existing[field]) { - changes.push({ - entityId: id, - field, - from: existing[field], - to: partial[field], - timestamp: now, - }) - } - } - - // Apply updates - Object.assign(existing, partial, { lastUpdate: now }) - - return changes - } - - /** - * Get entity by ID - */ - get(id: string): EntityState | null { - return this.entities.get(id) ?? null - } - - /** - * Get all player entities - */ - getPlayers(): EntityState[] { - return Array.from(this.entities.values()).filter(e => e.type === 'player') - } - - /** - * Get all entity IDs - */ - getAllIds(): string[] { - return Array.from(this.entities.keys()) - } - - /** - * Remove an entity - */ - remove(id: string): void { - this.entities.delete(id) - } - - /** - * Update self (bot) position for distance calculations - */ - updateSelfPosition(pos: Vec3): void { - this.selfPosition = pos - } - - /** - * Get self position - */ - getSelfPosition(): Vec3 | null { - return this.selfPosition - } - - /** - * Calculate distance from self to entity - */ - distanceToSelf(id: string): number | null { - if (!this.selfPosition) - return null - const entity = this.entities.get(id) - if (!entity) - return null - const dx = entity.position.x - this.selfPosition.x - const dy = entity.position.y - this.selfPosition.y - const dz = entity.position.z - this.selfPosition.z - return Math.sqrt(dx * dx + dy * dy + dz * dz) - } - - /** - * Clear all entities - */ - clear(): void { - this.entities.clear() - this.selfPosition = null - } -} diff --git a/services/minecraft/src/cognitive/world/index.ts b/services/minecraft/src/cognitive/world/index.ts deleted file mode 100644 index 7b31f369a..000000000 --- a/services/minecraft/src/cognitive/world/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { EntityStore } from './entity-store' -export { TemporalBuffer } from './temporal-buffer' -export * from './types' diff --git a/services/minecraft/src/cognitive/world/temporal-buffer.ts b/services/minecraft/src/cognitive/world/temporal-buffer.ts deleted file mode 100644 index 23ddba0d1..000000000 --- a/services/minecraft/src/cognitive/world/temporal-buffer.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { StateChange } from './types' - -/** - * Rolling window buffer of state changes per entity - */ -export class TemporalBuffer { - private buffer: Map = new Map() - private maxAge: number - - constructor(maxAgeMs: number = 5000) { - this.maxAge = maxAgeMs - } - - /** - * Record a state change - */ - record(change: StateChange): void { - const list = this.buffer.get(change.entityId) ?? [] - list.push(change) - this.buffer.set(change.entityId, list) - } - - /** - * Record multiple state changes - */ - recordAll(changes: StateChange[]): void { - for (const change of changes) { - this.record(change) - } - } - - /** - * Query changes for an entity since a given timestamp - */ - query(entityId: string, since: number): StateChange[] { - const list = this.buffer.get(entityId) ?? [] - return list.filter(c => c.timestamp >= since) - } - - /** - * Query changes for an entity by field - */ - queryField(entityId: string, field: string, since: number): StateChange[] { - return this.query(entityId, since).filter(c => c.field === field) - } - - /** - * Get all changes for an entity (within max age) - */ - getAll(entityId: string): StateChange[] { - const cutoff = Date.now() - this.maxAge - return this.query(entityId, cutoff) - } - - /** - * Prune old entries from all buffers - */ - prune(): void { - const cutoff = Date.now() - this.maxAge - - for (const [entityId, list] of this.buffer.entries()) { - const filtered = list.filter(c => c.timestamp >= cutoff) - if (filtered.length === 0) { - this.buffer.delete(entityId) - } - else { - this.buffer.set(entityId, filtered) - } - } - } - - /** - * Clear all history for an entity - */ - clearEntity(entityId: string): void { - this.buffer.delete(entityId) - } - - /** - * Clear all buffers - */ - clear(): void { - this.buffer.clear() - } -} diff --git a/services/minecraft/src/cognitive/world/types.ts b/services/minecraft/src/cognitive/world/types.ts deleted file mode 100644 index 1309627bd..000000000 --- a/services/minecraft/src/cognitive/world/types.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Vec3 } from 'vec3' - -/** - * State of an entity at a point in time - */ -export interface EntityState { - id: string - type: 'player' | 'mob' | 'item' - name?: string - - // Position & Movement - position: Vec3 - velocity: Vec3 - yaw: number - pitch: number - - // Status flags - isSneaking: boolean - isSprinting: boolean - onGround: boolean - - // Timestamps - firstSeen: number - lastUpdate: number -} - -/** - * A change in entity state - */ -export interface StateChange { - entityId: string - field: keyof EntityState - from: unknown - to: unknown - timestamp: number -} - -/** - * Belief about an entity's behavior - */ -export interface Belief { - confidence: number // 0-1 - data?: Record -} - -/** - * Definition of a pattern that computes beliefs - */ -export interface PatternDefinition { - id: string - category: 'social' | 'spatial' | 'threat' | 'neutral' - description: string - - compute: ( - entityId: string, - getState: (id: string) => EntityState | null, - getHistory: (id: string, since: number) => StateChange[], - selfPosition: Vec3 | null, - ) => Belief -} - -/** - * View of an entity for upper layers - */ -export interface EntityView { - id: string - name: string - type: EntityState['type'] - state: EntityState - beliefs: Record - distanceToSelf: number -}