feat(minecraft): implement reflex layer and related refactors

This commit is contained in:
Rin
2026-02-18 11:10:03 +08:00
committed by Neko Ayaka
parent e791d5c528
commit 585ff689bc
27 changed files with 650 additions and 249 deletions
@@ -1,21 +1 @@
import type { PlanStep } from '../../agents/planning/adapter'
export type ActionType = 'physical' | 'chat'
export interface BaseActionInstruction {
type: ActionType
description?: string
require_feedback?: boolean
}
export interface PhysicalActionInstruction extends BaseActionInstruction {
type: 'physical'
step: PlanStep
}
export interface ChatActionInstruction extends BaseActionInstruction {
type: 'chat'
message: string
}
export type ActionInstruction = PhysicalActionInstruction | ChatActionInstruction
export * from './types/index'
@@ -0,0 +1,21 @@
import type { PlanStep } from '../../../agents/planning/adapter'
export type ActionType = 'physical' | 'chat'
export interface BaseActionInstruction {
type: ActionType
description?: string
require_feedback?: boolean
}
export interface PhysicalActionInstruction extends BaseActionInstruction {
type: 'physical'
step: PlanStep
}
export interface ChatActionInstruction extends BaseActionInstruction {
type: 'chat'
message: string
}
export type ActionInstruction = PhysicalActionInstruction | ChatActionInstruction
@@ -1,28 +1,13 @@
import type { Vec3 } from 'vec3'
export interface SelfState {
status: 'idle' | 'moving' | 'working' | 'chatting' | 'busy'
location: Vec3 | null
holding: string | null
health: number
food: number
oxygen: number
}
export interface EnvironmentState {
time: string // 'day' | 'night' | 'sunset' | 'sunrise'
weather: 'clear' | 'rain' | 'thunder'
nearbyPlayers: string[]
nearbyEntities: string[] // significant entities (mobs, dropped items of interest)
lightLevel: number
export interface ContextViewState {
selfSummary: string
environmentSummary: string
}
export interface BlackboardState {
currentGoal: string
currentThought: string
executionStrategy: string
self: SelfState
environment: EnvironmentState
contextView: ContextViewState
}
export class Blackboard {
@@ -33,20 +18,9 @@ export class Blackboard {
currentGoal: 'Idle',
currentThought: 'I am waiting for something to happen.',
executionStrategy: 'Observe surroundings.',
self: {
status: 'idle',
location: null,
holding: null,
health: 20,
food: 20,
oxygen: 20,
},
environment: {
time: 'day',
weather: 'clear',
nearbyPlayers: [],
nearbyEntities: [],
lightLevel: 15,
contextView: {
selfSummary: 'Unknown',
environmentSummary: 'Unknown',
},
}
}
@@ -55,29 +29,22 @@ export class Blackboard {
public get goal(): string { return this._state.currentGoal }
public get thought(): string { return this._state.currentThought }
public get strategy(): string { return this._state.executionStrategy }
public get self(): SelfState { return this._state.self }
public get environment(): EnvironmentState { return this._state.environment }
public get selfSummary(): string { return this._state.contextView.selfSummary }
public get environmentSummary(): string { return this._state.contextView.environmentSummary }
// Setters (Partial updates allowed)
public update(updates: Partial<BlackboardState>): void {
this._state = { ...this._state, ...updates }
}
public updateSelf(updates: Partial<SelfState>): void {
this._state.self = { ...this._state.self, ...updates }
}
public updateEnvironment(updates: Partial<EnvironmentState>): void {
this._state.environment = { ...this._state.environment, ...updates }
public updateContextView(updates: Partial<ContextViewState>): void {
this._state.contextView = { ...this._state.contextView, ...updates }
}
public getSnapshot(): BlackboardState {
// Return a deep copy or safe reference?
// For now, return a shallow copy of the state structure
return {
...this._state,
self: { ...this._state.self }, // location (Vec3) is an object, but usually treated efficiently.
environment: { ...this._state.environment, nearbyPlayers: [...this._state.environment.nearbyPlayers], nearbyEntities: [...this._state.environment.nearbyEntities] },
contextView: { ...this._state.contextView },
}
}
}
@@ -5,12 +5,14 @@ import type { TaskExecutor } from '../action/task-executor'
import type { ActionInstruction } from '../action/types'
import type { EventManager } from '../perception/event-manager'
import type { BotEvent, MineflayerWithAgents, StimulusPayload } from '../types'
import type { ReflexManager } from '../reflex/reflex-manager'
import { system, user } from 'neuri/openai'
import { config } from '../../composables/config'
import { DebugService } from '../../debug-server'
import { Blackboard } from './blackboard'
import { buildConsciousContextView } from './context-view'
import { generateBrainSystemPrompt } from './prompts/brain-prompt'
interface BrainDeps {
@@ -18,6 +20,7 @@ interface BrainDeps {
neuri: Neuri
logger: Logg
taskExecutor: TaskExecutor
reflexManager: ReflexManager
}
interface LLMResponse {
@@ -186,19 +189,10 @@ export class Brain {
}
}
private updatePerception(bot: MineflayerWithAgents): void {
const pos = bot.bot.entity.position
this.blackboard.updateSelf({
location: pos,
health: bot.bot.health,
food: bot.bot.food,
})
this.blackboard.updateEnvironment({
time: bot.bot.time.isDay ? 'day' : 'night',
weather: bot.bot.isRaining ? 'rain' : 'clear',
nearbyPlayers: Object.keys(bot.bot.players).filter(p => p !== bot.bot.username),
})
private updatePerception(_bot: MineflayerWithAgents): void {
const ctx = this.deps.reflexManager.getContextSnapshot()
const view = buildConsciousContextView(ctx)
this.blackboard.updateContextView(view)
// Sync Blackboard to Debug
this.debugService.updateBlackboard(this.blackboard)
@@ -0,0 +1,19 @@
import type { ReflexContextState } from '../reflex/context'
export interface ConsciousContextView {
selfSummary: string
environmentSummary: string
}
export function buildConsciousContextView(ctx: ReflexContextState): ConsciousContextView {
const selfSummary = `Position ${String(ctx.self.location)} Health ${ctx.self.health}/20 Food ${ctx.self.food}/20 Oxygen ${ctx.self.oxygen}/20 Holding ${ctx.self.holding ?? 'nothing'}`
const players = ctx.environment.nearbyPlayers.map(p => p.name).join(',')
const entities = ctx.environment.nearbyEntities.map(e => e.name).join(',')
const environmentSummary = `${ctx.environment.time} ${ctx.environment.weather} Nearby players [${players}] Nearby entities [${entities}] Light ${ctx.environment.lightLevel}`
return {
selfSummary,
environmentSummary,
}
}
@@ -68,7 +68,7 @@ The following blackboard provides you with information about your current state:
Goal: "${blackboard.goal}"
Thought: "${blackboard.thought}"
Strategy: "${blackboard.strategy}"
Self: Position ${blackboard.self.location} Health ${blackboard.self.health}/20 Food ${blackboard.self.food}/20
Environment: ${blackboard.environment.time} ${blackboard.environment.weather} Nearby entities [${blackboard.environment.nearbyEntities.join(',')}]
Self: ${blackboard.selfSummary}
Environment: ${blackboard.environmentSummary}
`
}
@@ -81,7 +81,13 @@ export function createAgentContainer(options: {
taskExecutor: asClass(TaskExecutor).singleton(),
brain: asClass(Brain).singleton(),
brain: asClass(Brain)
.singleton()
.inject((c) => {
return {
reflexManager: c.resolve('reflexManager'),
}
}),
reflexManager: asClass(ReflexManager).singleton(),
})
@@ -44,6 +44,7 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
perceptionPipeline.init(botWithAgents)
tickHandler = ({ delta }) => {
reflexManager.tick(delta)
perceptionPipeline.tick(delta)
}
@@ -91,6 +92,9 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
const perceptionPipeline = container.resolve('perceptionPipeline')
perceptionPipeline.destroy()
const reflexManager = container.resolve('reflexManager')
reflexManager.destroy()
}
if (tickHandler) {
@@ -1,6 +1,6 @@
import type { Logg } from '@guiiai/logg'
import type { RawPerceptionEvent } from './raw-events'
import type { RawPerceptionEvent } from './types/raw-events'
import { LeakyBucket } from './leaky-bucket'
@@ -1,4 +1,4 @@
import type { RawPerceptionEvent } from './raw-events'
import type { RawPerceptionEvent } from './types/raw-events'
export type PerceptionFrameSource = 'minecraft'
@@ -10,7 +10,7 @@ import type {
SightedArmSwingEvent,
SightedEntityMovedEvent,
SightedSneakToggleEvent,
} from './raw-events'
} from './types/raw-events'
export class MineflayerPerceptionCollector {
private bot: MineflayerWithAgents | null = null
@@ -1,6 +1,6 @@
import type { PerceptionFrame } from './frame'
import type { RawPerceptionEvent } from './raw-events'
import type { PerceptionStage } from './stage'
import type { RawPerceptionEvent } from './types/raw-events'
import type { PerceptionStage } from './types/stage'
function getDistance(raw: RawPerceptionEvent): number | undefined {
return (raw as any).distance
@@ -12,7 +12,7 @@ import { createPerceptionFrameFromRawEvent } from './frame'
import { MineflayerPerceptionCollector } from './mineflayer-perception-collector'
import { NormalizerStage } from './normalizer-stage'
import { RawEventBuffer } from './raw-event-buffer'
import type { PerceptionStage } from './stage'
import type { PerceptionStage } from './types/stage'
export class PerceptionPipeline {
private readonly buffer = new RawEventBuffer()
@@ -1,74 +1 @@
import type { Vec3 } from 'vec3'
export type PerceptionModality = 'sighted' | 'heard' | 'felt'
export interface RawPerceptionEventBase {
modality: PerceptionModality
timestamp: number
source: 'minecraft'
pos?: Vec3
}
export interface SightedEntityMovedEvent extends RawPerceptionEventBase {
modality: 'sighted'
kind: 'entity_moved'
entityType: 'player' | 'mob'
entityId: string
displayName?: string
distance: number
hasLineOfSight: boolean
}
export interface SightedArmSwingEvent extends RawPerceptionEventBase {
modality: 'sighted'
kind: 'arm_swing'
entityType: 'player'
entityId: string
displayName?: string
distance: number
hasLineOfSight: boolean
}
export interface SightedSneakToggleEvent extends RawPerceptionEventBase {
modality: 'sighted'
kind: 'sneak_toggle'
entityType: 'player'
entityId: string
displayName?: string
distance: number
hasLineOfSight: boolean
sneaking: boolean
}
export type SightedEvent = SightedEntityMovedEvent | SightedArmSwingEvent | SightedSneakToggleEvent
export interface HeardSoundEvent extends RawPerceptionEventBase {
modality: 'heard'
kind: 'sound'
soundId: string
distance: number
inferredEntityType?: 'player' | 'mob'
inferredEntityId?: string
}
export type HeardEvent = HeardSoundEvent
export interface FeltDamageTakenEvent extends RawPerceptionEventBase {
modality: 'felt'
kind: 'damage_taken'
amount?: number
attackerEntityType?: 'player' | 'mob'
attackerEntityId?: string
distance?: number
}
export interface FeltItemCollectedEvent extends RawPerceptionEventBase {
modality: 'felt'
kind: 'item_collected'
itemName: string
count?: number
}
export type FeltEvent = FeltDamageTakenEvent | FeltItemCollectedEvent
export type RawPerceptionEvent = SightedEvent | HeardEvent | FeltEvent
export * from './types/raw-events'
@@ -1,7 +1 @@
import type { PerceptionFrame } from './frame'
export interface PerceptionStage {
name: string
tick?: (deltaMs: number) => void
handle: (frame: PerceptionFrame) => PerceptionFrame | null
}
export * from './types/stage'
@@ -0,0 +1,74 @@
import type { Vec3 } from 'vec3'
export type PerceptionModality = 'sighted' | 'heard' | 'felt'
export interface RawPerceptionEventBase {
modality: PerceptionModality
timestamp: number
source: 'minecraft'
pos?: Vec3
}
export interface SightedEntityMovedEvent extends RawPerceptionEventBase {
modality: 'sighted'
kind: 'entity_moved'
entityType: 'player' | 'mob'
entityId: string
displayName?: string
distance: number
hasLineOfSight: boolean
}
export interface SightedArmSwingEvent extends RawPerceptionEventBase {
modality: 'sighted'
kind: 'arm_swing'
entityType: 'player'
entityId: string
displayName?: string
distance: number
hasLineOfSight: boolean
}
export interface SightedSneakToggleEvent extends RawPerceptionEventBase {
modality: 'sighted'
kind: 'sneak_toggle'
entityType: 'player'
entityId: string
displayName?: string
distance: number
hasLineOfSight: boolean
sneaking: boolean
}
export type SightedEvent = SightedEntityMovedEvent | SightedArmSwingEvent | SightedSneakToggleEvent
export interface HeardSoundEvent extends RawPerceptionEventBase {
modality: 'heard'
kind: 'sound'
soundId: string
distance: number
inferredEntityType?: 'player' | 'mob'
inferredEntityId?: string
}
export type HeardEvent = HeardSoundEvent
export interface FeltDamageTakenEvent extends RawPerceptionEventBase {
modality: 'felt'
kind: 'damage_taken'
amount?: number
attackerEntityType?: 'player' | 'mob'
attackerEntityId?: string
distance?: number
}
export interface FeltItemCollectedEvent extends RawPerceptionEventBase {
modality: 'felt'
kind: 'item_collected'
itemName: string
count?: number
}
export type FeltEvent = FeltDamageTakenEvent | FeltItemCollectedEvent
export type RawPerceptionEvent = SightedEvent | HeardEvent | FeltEvent
@@ -0,0 +1,7 @@
import type { PerceptionFrame } from '../frame'
export interface PerceptionStage {
name: string
tick?: (deltaMs: number) => void
handle: (frame: PerceptionFrame) => PerceptionFrame | null
}
@@ -0,0 +1 @@
export * from './types/behavior'
@@ -0,0 +1,40 @@
import type { ReflexBehavior } from '../types/behavior'
export const greetingBehavior: ReflexBehavior = {
id: 'greeting',
modes: ['social'],
cooldownMs: 10_000,
when: (ctx) => {
const msg = ctx.social.lastMessage
if (!msg)
return false
const lower = msg.toLowerCase().trim()
return lower === 'hi' || lower === 'hello'
},
score: (ctx) => {
if (!ctx.social.lastSpeaker)
return 0
const lastGreetAt = ctx.social.lastGreetingAtBySpeaker[ctx.social.lastSpeaker]
if (lastGreetAt && ctx.now - lastGreetAt < 10_000)
return 0
return 10
},
run: ({ bot, context }) => {
const snap = context.getSnapshot()
const speaker = snap.social.lastSpeaker
if (!speaker)
return
bot.bot.chat('Hi there! (Reflex)')
context.updateSocial({
lastGreetingAtBySpeaker: {
...snap.social.lastGreetingAtBySpeaker,
[speaker]: snap.now,
},
})
},
}
@@ -0,0 +1,110 @@
import type { Vec3 } from 'vec3'
export interface ReflexSelfState {
location: Vec3 | null
holding: string | null
health: number
food: number
oxygen: number
}
export interface ReflexEnvironmentState {
time: 'day' | 'night' | 'sunset' | 'sunrise'
weather: 'clear' | 'rain' | 'thunder'
nearbyPlayers: Array<{ name: string, distance?: number }>
nearbyEntities: Array<{ name: string, distance?: number, kind?: string }>
lightLevel: number
}
export interface ReflexSocialState {
lastSpeaker: string | null
lastMessage: string | null
lastMessageAt: number | null
lastGreetingAtBySpeaker: Record<string, number>
}
export interface ReflexThreatState {
threatScore: number
lastThreatAt: number | null
lastThreatSource: string | null
}
export interface ReflexContextState {
now: number
self: ReflexSelfState
environment: ReflexEnvironmentState
social: ReflexSocialState
threat: ReflexThreatState
}
export class ReflexContext {
private state: ReflexContextState
constructor() {
this.state = {
now: Date.now(),
self: {
location: null,
holding: null,
health: 20,
food: 20,
oxygen: 20,
},
environment: {
time: 'day',
weather: 'clear',
nearbyPlayers: [],
nearbyEntities: [],
lightLevel: 15,
},
social: {
lastSpeaker: null,
lastMessage: null,
lastMessageAt: null,
lastGreetingAtBySpeaker: {},
},
threat: {
threatScore: 0,
lastThreatAt: null,
lastThreatSource: null,
},
}
}
public getSnapshot(): ReflexContextState {
return {
...this.state,
self: { ...this.state.self },
environment: {
...this.state.environment,
nearbyPlayers: this.state.environment.nearbyPlayers.map(p => ({ ...p })),
nearbyEntities: this.state.environment.nearbyEntities.map(e => ({ ...e })),
},
social: {
...this.state.social,
lastGreetingAtBySpeaker: { ...this.state.social.lastGreetingAtBySpeaker },
},
threat: { ...this.state.threat },
}
}
public updateNow(now: number): void {
this.state.now = now
}
public updateSelf(patch: Partial<ReflexSelfState>): void {
this.state.self = { ...this.state.self, ...patch }
}
public updateEnvironment(patch: Partial<ReflexEnvironmentState>): void {
this.state.environment = { ...this.state.environment, ...patch }
}
public updateSocial(patch: Partial<ReflexSocialState>): void {
this.state.social = { ...this.state.social, ...patch }
}
public updateThreat(patch: Partial<ReflexThreatState>): void {
this.state.threat = { ...this.state.threat, ...patch }
}
}
@@ -0,0 +1,13 @@
import type { ReflexContextState } from './context'
export type ReflexModeId = 'idle' | 'social' | 'alert'
export function selectMode(ctx: ReflexContextState): ReflexModeId {
if (ctx.threat.threatScore > 0)
return 'alert'
if (ctx.social.lastMessageAt && ctx.now - ctx.social.lastMessageAt < 15_000)
return 'social'
return 'idle'
}
@@ -0,0 +1,61 @@
import { describe, expect, it, vi } from 'vitest'
import { EventManager } from '../perception/event-manager'
import { ReflexManager } from './reflex-manager'
function makeLogger() {
return {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
withFields: vi.fn(() => makeLogger()),
withError: vi.fn(() => makeLogger()),
} as any
}
function makeBot() {
const bot = {
bot: {
username: 'bot',
chat: vi.fn(),
entity: {
position: { x: 0, y: 0, z: 0 },
},
health: 20,
food: 20,
oxygenLevel: 20,
heldItem: null,
time: { isDay: true },
isRaining: false,
players: {},
},
}
return bot as any
}
describe('ReflexManager', () => {
it('handles greeting via reflex and marks stimulus event handled', () => {
const eventManager = new EventManager()
const logger = makeLogger()
const reflex = new ReflexManager({ eventManager, logger })
const bot = makeBot()
reflex.init(bot)
const stimulus: any = {
type: 'stimulus',
payload: { content: 'hello' },
source: { type: 'minecraft', id: 'alice' },
timestamp: Date.now(),
}
eventManager.emit(stimulus)
expect(stimulus.handled).toBe(true)
expect(bot.bot.chat).toHaveBeenCalled()
reflex.destroy()
})
})
@@ -3,38 +3,69 @@ import type { Logg } from '@guiiai/logg'
import type { EventManager } from '../perception/event-manager'
import type { BotEvent, MineflayerWithAgents, StimulusPayload } from '../types'
import type { ReflexContextState } from './context'
import { greetingBehavior } from './behaviors/greeting'
import { ReflexRuntime } from './runtime'
export class ReflexManager {
private bot: MineflayerWithAgents | null = null
private readonly runtime: ReflexRuntime
private readonly onStimulusHandler = (event: BotEvent<StimulusPayload>) => {
this.onStimulus(event)
}
constructor(
private readonly deps: {
eventManager: EventManager
logger: Logg
},
) {}
public init(bot: MineflayerWithAgents): void {
// Listen to stimuli as a "subconscious" filter
this.deps.eventManager.on<StimulusPayload>('stimulus', (event) => {
this.onStimulus(bot, event)
) {
this.runtime = new ReflexRuntime({
logger: this.deps.logger,
})
// TODO: Listen to world_update for physical reflexes (dodge, flee)
this.runtime.registerBehavior(greetingBehavior)
}
private onStimulus(bot: MineflayerWithAgents, event: BotEvent<StimulusPayload>): void {
const { content } = event.payload
const lowerContent = content.toLowerCase().trim()
public init(bot: MineflayerWithAgents): void {
this.bot = bot
this.deps.eventManager.on<StimulusPayload>('stimulus', this.onStimulusHandler)
}
if (lowerContent === 'hi' || lowerContent === 'hello') {
this.deps.logger.log('Reflex: Handling greeting')
public destroy(): void {
this.deps.eventManager.off<StimulusPayload>('stimulus', this.onStimulusHandler)
this.bot = null
}
const reply = 'Hi there! (Reflex)'
if (event.source.reply) {
event.source.reply(reply)
}
else {
bot.bot.chat(reply)
}
public tick(deltaMs: number): void {
if (!this.bot)
return
this.runtime.tick(this.bot, deltaMs)
}
public getContextSnapshot(): ReflexContextState {
return this.runtime.getContext().getSnapshot()
}
private onStimulus(event: BotEvent<StimulusPayload>): void {
const bot = this.bot
if (!bot)
return
const now = Date.now()
this.runtime.getContext().updateNow(now)
this.runtime.getContext().updateSocial({
lastSpeaker: event.source.id,
lastMessage: event.payload.content,
lastMessageAt: now,
})
const behaviorId = this.runtime.tick(bot, 0)
if (behaviorId) {
event.handled = true
}
}
@@ -0,0 +1,129 @@
import type { Logg } from '@guiiai/logg'
import type { MineflayerWithAgents } from '../types'
import { ReflexContext } from './context'
import type { ReflexBehavior } from './types/behavior'
import { selectMode, type ReflexModeId } from './modes'
export class ReflexRuntime {
private readonly context = new ReflexContext()
private readonly behaviors: ReflexBehavior[] = []
private readonly runHistory = new Map<string, { lastRunAt: number }>()
private mode: ReflexModeId = 'idle'
private activeBehaviorId: string | null = null
private activeBehaviorUntil: number | null = null
public constructor(
private readonly deps: {
logger: Logg
},
) { }
public getContext(): ReflexContext {
return this.context
}
public getMode(): ReflexModeId {
return this.mode
}
public getActiveBehaviorId(): string | null {
return this.activeBehaviorId
}
public registerBehavior(behavior: ReflexBehavior): void {
this.behaviors.push(behavior)
}
public tick(bot: MineflayerWithAgents, deltaMs: number): string | null {
const now = Date.now()
this.context.updateNow(now)
// TODO: future refactor: update ReflexContext via world_update/self_update events instead of polling Mineflayer state.
this.context.updateSelf({
location: bot.bot.entity.position,
health: bot.bot.health,
food: bot.bot.food,
oxygen: bot.bot.oxygenLevel,
holding: bot.bot.heldItem?.name ?? null,
})
this.context.updateEnvironment({
time: bot.bot.time.isDay ? 'day' : 'night',
weather: bot.bot.isRaining ? 'rain' : 'clear',
nearbyPlayers: Object.keys(bot.bot.players)
.filter(p => p !== bot.bot.username)
.map(name => ({ name })),
})
this.mode = selectMode(this.context.getSnapshot())
if (this.activeBehaviorUntil && now < this.activeBehaviorUntil)
return null
this.activeBehaviorId = null
this.activeBehaviorUntil = null
const ctx = this.context.getSnapshot()
let best: { behavior: ReflexBehavior, score: number } | null = null
for (const behavior of this.behaviors) {
if (!behavior.modes.includes(this.mode))
continue
if (!behavior.when(ctx))
continue
const score = behavior.score(ctx)
if (score <= 0)
continue
const history = this.runHistory.get(behavior.id)
const cooldownMs = behavior.cooldownMs ?? 0
if (history && cooldownMs > 0 && now - history.lastRunAt < cooldownMs)
continue
if (!best || score > best.score)
best = { behavior, score }
}
if (!best)
return null
this.activeBehaviorId = best.behavior.id
this.runHistory.set(best.behavior.id, { lastRunAt: now })
try {
const maybePromise = best.behavior.run({ bot, context: this.context })
if (maybePromise && typeof (maybePromise as any).then === 'function') {
this.activeBehaviorUntil = now + Math.max(deltaMs, 50)
void (maybePromise as Promise<void>).finally(() => {
// Behavior ends naturally; next tick can run a new one.
this.activeBehaviorUntil = null
this.activeBehaviorId = null
})
}
else {
// Synchronous behavior ends immediately.
this.activeBehaviorId = null
}
this.deps.logger.withFields({
mode: this.mode,
behavior: best.behavior.id,
score: best.score,
}).log('ReflexRuntime: selected')
return best.behavior.id
}
catch (err) {
this.deps.logger.withError(err as Error).error('ReflexRuntime: behavior failed')
this.activeBehaviorId = null
this.activeBehaviorUntil = null
return null
}
}
}
@@ -0,0 +1,22 @@
import type { MineflayerWithAgents } from '../../types'
import type { ReflexContext } from '../context'
import type { ReflexModeId } from '../modes'
export interface ReflexApi {
bot: MineflayerWithAgents
context: ReflexContext
}
export interface ReflexBehavior {
id: string
modes: ReflexModeId[]
cooldownMs?: number
when: (ctx: ReturnType<ReflexContext['getSnapshot']>) => boolean
score: (ctx: ReturnType<ReflexContext['getSnapshot']>) => number
run: (api: ReflexApi) => Promise<void> | void
}
export interface BehaviorRunRecord {
lastRunAt: number
}
+1 -61
View File
@@ -1,61 +1 @@
import type { Client } from '@proj-airi/server-sdk'
import type { Neuri } from 'neuri'
import type { Mineflayer } from '../libs/mineflayer'
import type { ActionAgent, ChatAgent, PlanningAgent } from '../libs/mineflayer/base-agent'
export interface LLMConfig {
agent: Neuri
model?: string
retryLimit?: number
delayInterval?: number
maxContextLength?: number
}
export interface LLMResponse {
content: string
usage?: any
}
export interface MineflayerWithAgents extends Mineflayer {
planning: PlanningAgent
action: ActionAgent
chat: ChatAgent
}
export interface CognitiveEngineOptions {
agent: Neuri
airiClient: Client
}
// TODO: currently stimulus is just chat events, consider renaming to 'input' or 'user_interaction'
export type EventCategory = 'stimulus' | 'perception' | 'feedback' | 'world_update' | 'system_alert'
export interface BotEventSource {
type: 'minecraft' | 'airi' | 'system'
id: string // Agent/Source identifier
reply?: (message: string) => void
}
export interface BotEvent<T = any> {
type: EventCategory
payload: T
source: BotEventSource
timestamp: number
// Layered Architecture Metadata
priority?: number // Higher is more urgent
handled?: boolean // Set by Reflex layer to inhibit Conscious layer
}
export interface StimulusPayload {
content: string
metadata?: {
entity?: any // prismarine-entity Entity
displayName?: string
}
}
export interface WorldUpdatePayload {
event: string
data: any
}
export * from './types/index'
@@ -0,0 +1,61 @@
import type { Client } from '@proj-airi/server-sdk'
import type { Neuri } from 'neuri'
import type { Mineflayer } from '../../libs/mineflayer'
import type { ActionAgent, ChatAgent, PlanningAgent } from '../../libs/mineflayer/base-agent'
export interface LLMConfig {
agent: Neuri
model?: string
retryLimit?: number
delayInterval?: number
maxContextLength?: number
}
export interface LLMResponse {
content: string
usage?: any
}
export interface MineflayerWithAgents extends Mineflayer {
planning: PlanningAgent
action: ActionAgent
chat: ChatAgent
}
export interface CognitiveEngineOptions {
agent: Neuri
airiClient: Client
}
// TODO: currently stimulus is just chat events, consider renaming to 'input' or 'user_interaction'
export type EventCategory = 'stimulus' | 'perception' | 'feedback' | 'world_update' | 'system_alert'
export interface BotEventSource {
type: 'minecraft' | 'airi' | 'system'
id: string // Agent/Source identifier
reply?: (message: string) => void
}
export interface BotEvent<T = any> {
type: EventCategory
payload: T
source: BotEventSource
timestamp: number
// Layered Architecture Metadata
priority?: number // Higher is more urgent
handled?: boolean // Set by Reflex layer to inhibit Conscious layer
}
export interface StimulusPayload {
content: string
metadata?: {
entity?: any // prismarine-entity Entity
displayName?: string
}
}
export interface WorldUpdatePayload {
event: string
data: any
}