chore(minecraft): make linter happy

This commit is contained in:
Rin
2026-02-18 11:10:04 +08:00
committed by Neko Ayaka
parent 3ba23dda5b
commit f1c06e55a4
16 changed files with 319 additions and 322 deletions
@@ -3,19 +3,19 @@ import type { PlanStep } from '../../../agents/planning/adapter'
export type ActionType = 'physical' | 'chat'
export interface BaseActionInstruction {
type: ActionType
description?: string
require_feedback?: boolean
type: ActionType
description?: string
require_feedback?: boolean
}
export interface PhysicalActionInstruction extends BaseActionInstruction {
type: 'physical'
step: PlanStep
type: 'physical'
step: PlanStep
}
export interface ChatActionInstruction extends BaseActionInstruction {
type: 'chat'
message: string
type: 'chat'
message: string
}
export type ActionInstruction = PhysicalActionInstruction | ChatActionInstruction
@@ -1,19 +1,19 @@
import type { ReflexContextState } from '../reflex/context'
export interface ConsciousContextView {
selfSummary: string
environmentSummary: string
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 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}`
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,
}
return {
selfSummary,
environmentSummary,
}
}
@@ -3,77 +3,77 @@ import { describe, expect, it } from 'vitest'
import { AttentionDetector } from './attention-detector'
function makeLogger() {
const logger: any = {
withFields: () => logger,
withError: () => logger,
log: () => { },
warn: () => { },
error: () => { },
}
return logger
const logger: any = {
withFields: () => logger,
withError: () => logger,
log: () => { },
warn: () => { },
error: () => { },
}
return logger
}
describe('AttentionDetector', () => {
it('emits punch attention after 3 arm_swing events', () => {
const emitted: any[] = []
const detector = new AttentionDetector({
logger: makeLogger(),
onAttention: payload => emitted.push(payload),
})
const base: any = {
modality: 'sighted',
kind: 'arm_swing',
entityType: 'player',
entityId: 'p1',
displayName: 'alice',
distance: 10,
hasLineOfSight: true,
timestamp: Date.now(),
source: 'minecraft',
}
detector.ingest({ ...base })
detector.ingest({ ...base })
detector.ingest({ ...base })
expect(emitted.length).toBe(1)
expect(emitted[0]).toMatchObject({
kind: 'player',
playerAction: 'punch',
playerName: 'alice',
})
describe('attentionDetector', () => {
it('emits punch attention after 3 arm_swing events', () => {
const emitted: any[] = []
const detector = new AttentionDetector({
logger: makeLogger(),
onAttention: payload => emitted.push(payload),
})
it('emits sound attention (gated per soundId)', () => {
const emitted: any[] = []
const detector = new AttentionDetector({
logger: makeLogger(),
onAttention: payload => emitted.push(payload),
})
const base: any = {
modality: 'sighted',
kind: 'arm_swing',
entityType: 'player',
entityId: 'p1',
displayName: 'alice',
distance: 10,
hasLineOfSight: true,
timestamp: Date.now(),
source: 'minecraft',
}
detector.ingest({
modality: 'heard',
kind: 'sound',
soundId: 's1',
distance: 5,
timestamp: Date.now(),
source: 'minecraft',
} as any)
detector.ingest({ ...base })
detector.ingest({ ...base })
detector.ingest({ ...base })
detector.ingest({
modality: 'heard',
kind: 'sound',
soundId: 's1',
distance: 5,
timestamp: Date.now(),
source: 'minecraft',
} as any)
expect(emitted.length).toBe(1)
expect(emitted[0]).toMatchObject({
kind: 'player',
playerAction: 'sound',
})
expect(emitted.length).toBe(1)
expect(emitted[0]).toMatchObject({
kind: 'player',
playerAction: 'punch',
playerName: 'alice',
})
})
it('emits sound attention (gated per soundId)', () => {
const emitted: any[] = []
const detector = new AttentionDetector({
logger: makeLogger(),
onAttention: payload => emitted.push(payload),
})
detector.ingest({
modality: 'heard',
kind: 'sound',
soundId: 's1',
distance: 5,
timestamp: Date.now(),
source: 'minecraft',
} as any)
detector.ingest({
modality: 'heard',
kind: 'sound',
soundId: 's1',
distance: 5,
timestamp: Date.now(),
source: 'minecraft',
} as any)
expect(emitted.length).toBe(1)
expect(emitted[0]).toMatchObject({
kind: 'player',
playerAction: 'sound',
})
})
})
@@ -18,6 +18,7 @@ export class MineflayerPerceptionCollector {
event: string
handler: (...args: any[]) => void
}> = []
private lastSelfHealth: number | null = null
private lastStatsAt = 0
@@ -231,8 +232,9 @@ export class MineflayerPerceptionCollector {
for (const { event, handler } of this.listeners) {
try {
(this.bot.bot as any).off?.(event, handler)
(this.bot.bot as any).removeListener?.(event, handler)
const b = this.bot.bot as any
b.off?.(event, handler)
b.removeListener?.(event, handler)
}
catch (err) {
this.deps.logger.withError(err as Error).error('MineflayerPerceptionCollector: failed to remove listener')
@@ -293,5 +295,4 @@ export class MineflayerPerceptionCollector {
return null
}
}
}
@@ -2,15 +2,14 @@ import type { Logg } from '@guiiai/logg'
import type { MineflayerWithAgents } from '../types'
import type { EventManager } from './event-manager'
import type { PerceptionFrame } from './frame'
import type { PerceptionSignal } from './types/signals'
import type { PerceptionStage } from './types/stage'
import { AttentionDetector } from './attention-detector'
import { createPerceptionFrameFromRawEvent } from './frame'
import { MineflayerPerceptionCollector } from './mineflayer-perception-collector'
import { RawEventBuffer } from './raw-event-buffer'
import type { PerceptionStage } from './types/stage'
export class PerceptionPipeline {
private readonly buffer = new RawEventBuffer()
@@ -3,70 +3,70 @@ import type { Vec3 } from 'vec3'
export type PerceptionModality = 'sighted' | 'heard' | 'felt'
export interface RawPerceptionEventBase {
modality: PerceptionModality
timestamp: number
source: 'minecraft'
pos?: Vec3
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
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
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
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
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
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
modality: 'felt'
kind: 'item_collected'
itemName: string
count?: number
}
export type FeltEvent = FeltDamageTakenEvent | FeltItemCollectedEvent
@@ -1,19 +1,18 @@
export type PerceptionSignalType =
| 'chat_message'
| 'entity_attention' // e.g. someone waving, teabagging
| 'environmental_anomaly' // e.g. sudden loud sound
| 'saliency_high' // generic high saliency event
export type PerceptionSignalType
= | 'chat_message'
| 'entity_attention' // e.g. someone waving, teabagging
| 'environmental_anomaly' // e.g. sudden loud sound
| 'saliency_high' // generic high saliency event
export interface PerceptionSignal {
type: PerceptionSignalType
description: string // Textual summary for LLM
type: PerceptionSignalType
description: string // Textual summary for LLM
// Contextual Data
sourceId?: string // Who/What caused this
confidence?: number // 0-1
timestamp: number
// Contextual Data
sourceId?: string // Who/What caused this
confidence?: number // 0-1
timestamp: number
// Structured Data (for logic)
metadata: Record<string, any>
// Structured Data (for logic)
metadata: Record<string, any>
}
@@ -1,7 +1,7 @@
import type { PerceptionFrame } from '../frame'
export interface PerceptionStage {
name: string
tick?: (deltaMs: number) => void
handle: (frame: PerceptionFrame) => PerceptionFrame | null
name: string
tick?: (deltaMs: number) => void
handle: (frame: PerceptionFrame) => PerceptionFrame | null
}
@@ -1,110 +1,110 @@
import type { Vec3 } from 'vec3'
export interface ReflexSelfState {
location: Vec3 | null
holding: string | null
health: number
food: number
oxygen: number
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
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>
lastSpeaker: string | null
lastMessage: string | null
lastMessageAt: number | null
lastGreetingAtBySpeaker: Record<string, number>
}
export interface ReflexThreatState {
threatScore: number
lastThreatAt: number | null
lastThreatSource: string | null
threatScore: number
lastThreatAt: number | null
lastThreatSource: string | null
}
export interface ReflexContextState {
now: number
self: ReflexSelfState
environment: ReflexEnvironmentState
social: ReflexSocialState
threat: ReflexThreatState
now: number
self: ReflexSelfState
environment: ReflexEnvironmentState
social: ReflexSocialState
threat: ReflexThreatState
}
export class ReflexContext {
private state: ReflexContextState
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,
},
}
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 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 updateNow(now: number): void {
this.state.now = now
}
public updateSelf(patch: Partial<ReflexSelfState>): void {
this.state.self = { ...this.state.self, ...patch }
}
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 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 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 }
}
public updateThreat(patch: Partial<ReflexThreatState>): void {
this.state.threat = { ...this.state.threat, ...patch }
}
}
@@ -3,11 +3,11 @@ 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.threat.threatScore > 0)
return 'alert'
if (ctx.social.lastMessageAt && ctx.now - ctx.social.lastMessageAt < 15_000)
return 'social'
if (ctx.social.lastMessageAt && ctx.now - ctx.social.lastMessageAt < 15_000)
return 'social'
return 'idle'
return 'idle'
}
@@ -1,61 +1,60 @@
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
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: {},
},
}
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
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 })
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 bot = makeBot()
reflex.init(bot)
const stimulus: any = {
type: 'stimulus',
payload: { content: 'hello' },
source: { type: 'minecraft', id: 'alice' },
timestamp: Date.now(),
}
const stimulus: any = {
type: 'stimulus',
payload: { content: 'hello' },
source: { type: 'minecraft', id: 'alice' },
timestamp: Date.now(),
}
eventManager.emit(stimulus)
eventManager.emit(stimulus)
expect(stimulus.handled).toBe(true)
expect(bot.bot.chat).toHaveBeenCalled()
expect(stimulus.handled).toBe(true)
expect(bot.bot.chat).toHaveBeenCalled()
reflex.destroy()
})
reflex.destroy()
})
})
@@ -3,7 +3,6 @@ import type { Logg } from '@guiiai/logg'
import type { EventManager } from '../perception/event-manager'
import type { PerceptionSignal } from '../perception/types/signals'
import type { BotEvent, MineflayerWithAgents } from '../types'
import type { ReflexContextState } from './context'
import { greetingBehavior } from './behaviors/greeting'
@@ -1,10 +1,11 @@
import type { Logg } from '@guiiai/logg'
import type { MineflayerWithAgents } from '../types'
import type { ReflexModeId } from './modes'
import type { ReflexBehavior } from './types/behavior'
import { ReflexContext } from './context'
import type { ReflexBehavior } from './types/behavior'
import { selectMode, type ReflexModeId } from './modes'
import { selectMode } from './modes'
export class ReflexRuntime {
private readonly context = new ReflexContext()
@@ -1,22 +1,21 @@
import type { MineflayerWithAgents } from '../../types'
import type { ReflexContext } from '../context'
import type { ReflexModeId } from '../modes'
export interface ReflexApi {
bot: MineflayerWithAgents
context: ReflexContext
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
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
lastRunAt: number
}
+24 -24
View File
@@ -5,49 +5,49 @@ 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
agent: Neuri
model?: string
retryLimit?: number
delayInterval?: number
maxContextLength?: number
}
export interface LLMResponse {
content: string
usage?: any
content: string
usage?: any
}
export interface MineflayerWithAgents extends Mineflayer {
planning: PlanningAgent
action: ActionAgent
chat: ChatAgent
planning: PlanningAgent
action: ActionAgent
chat: ChatAgent
}
export interface CognitiveEngineOptions {
agent: Neuri
airiClient: Client
agent: Neuri
airiClient: Client
}
// TODO: currently stimulus is just chat events, consider renaming to 'input' or 'user_interaction'
export type EventCategory = 'perception' | 'feedback' | 'world_update' | 'system_alert'
export interface BotEventSource {
type: 'minecraft' | 'airi' | 'system'
id: string // Agent/Source identifier
reply?: (message: string) => void
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
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 WorldUpdatePayload {
event: string
data: any
event: string
data: any
}
+7 -7
View File
@@ -1,10 +1,10 @@
export type ActionErrorCode =
| 'RESOURCE_MISSING'
| 'CRAFTING_FAILED'
| 'NAVIGATION_FAILED'
| 'INTERRUPTED'
| 'INVENTORY_FULL'
| 'UNKNOWN'
export type ActionErrorCode
= | 'RESOURCE_MISSING'
| 'CRAFTING_FAILED'
| 'NAVIGATION_FAILED'
| 'INTERRUPTED'
| 'INVENTORY_FULL'
| 'UNKNOWN'
export class ActionError extends Error {
public readonly code: ActionErrorCode