Skip to content

Commit 7158c55

Browse files
shinohara-rinnekomeowww
authored andcommitted
feat(minecraft): yet another overhaul...
i honestly don't know what to feel about this
1 parent c46b3fa commit 7158c55

28 files changed

+2607
-6
lines changed

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ catalog:
9393
vue-sonner: 2.0.9
9494
ws: ^8.18.3
9595
xsschema: 0.4.0-beta.13
96+
yaml: ^2.8.2
9697
zod: ^4.3.5
9798

9899
catalogs:

services/minecraft/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"mineflayer-pathfinder": "^2.4.5",
2727
"mineflayer-pvp": "^1.3.2",
2828
"mineflayer-tool": "^1.2.0",
29+
"nanoid": "catalog:",
2930
"neuri": "^0.2.1",
3031
"prismarine-block": "^1.22.0",
3132
"prismarine-entity": "^2.5.0",
@@ -37,6 +38,7 @@
3738
"vec3": "^0.1.10",
3839
"zod": "^4.3.5",
3940
"ws": "catalog:",
41+
"yaml": "catalog:",
4042
"zod-to-json-schema": "^3.25.1"
4143
},
4244
"devDependencies": {

services/minecraft/src/cognitive/container.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Logg } from '@guiiai/logg'
22
import type { Neuri } from 'neuri'
33

4+
import type { EventBus, RuleEngine } from './os'
5+
46
import { useLogg } from '@guiiai/logg'
57
import { asClass, asFunction, createContainer, InjectionMode } from 'awilix'
68

@@ -9,12 +11,15 @@ import { ChatAgentImpl } from '../agents/chat'
911
import { PlanningAgentImpl } from '../agents/planning'
1012
import { TaskExecutor } from './action/task-executor'
1113
import { Brain } from './conscious/brain'
14+
import { createEventBus, createRuleEngine } from './os'
1215
import { EventManager } from './perception/event-manager'
1316
import { PerceptionPipeline } from './perception/pipeline'
1417
import { ReflexManager } from './reflex/reflex-manager'
1518

1619
export interface ContainerServices {
1720
logger: Logg
21+
eventBus: EventBus
22+
ruleEngine: RuleEngine
1823
actionAgent: ActionAgentImpl
1924
planningAgent: PlanningAgentImpl
2025
chatAgent: ChatAgentImpl
@@ -43,6 +48,28 @@ export function createAgentContainer(options: {
4348
// Register neuri client
4449
neuri: asFunction(() => options.neuri).singleton(),
4550

51+
// Register EventBus (Cognitive OS core)
52+
eventBus: asFunction(() =>
53+
createEventBus({
54+
logger: useLogg('eventBus').useGlobalConfig(),
55+
config: { historySize: 10000 },
56+
}),
57+
).singleton(),
58+
59+
// Register RuleEngine (YAML rules processing)
60+
ruleEngine: asFunction(({ eventBus }) => {
61+
const engine = createRuleEngine({
62+
eventBus,
63+
logger: useLogg('ruleEngine').useGlobalConfig(),
64+
config: {
65+
rulesDir: new URL('../rules', import.meta.url).pathname,
66+
slotMs: 20,
67+
},
68+
})
69+
engine.init()
70+
return engine
71+
}).singleton(),
72+
4673
// Register agents
4774
actionAgent: asClass(ActionAgentImpl)
4875
.singleton()

services/minecraft/src/cognitive/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { MineflayerPlugin } from '../libs/mineflayer'
22
import type { CognitiveEngineOptions, MineflayerWithAgents } from './types'
33

44
import { config } from '../composables/config'
5+
import { DebugService } from '../debug'
56
import { ChatMessageHandler } from '../libs/mineflayer'
67
import { createAgentContainer } from './container'
78
import { createPerceptionFrameFromChat } from './perception/frame'
@@ -48,6 +49,21 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
4849
// Initialize perception pipeline (raw events + detectors)
4950
perceptionPipeline.init(botWithAgents)
5051

52+
// Resolve EventBus and subscribe to forward events to debug timeline
53+
const eventBus = container.resolve('eventBus')
54+
eventBus.subscribe('*', (event) => {
55+
// Forward to debug service for timeline visualization
56+
DebugService.getInstance().emitTrace({
57+
id: event.id,
58+
traceId: event.traceId,
59+
parentId: event.parentId,
60+
type: event.type,
61+
payload: event.payload,
62+
timestamp: event.timestamp,
63+
source: event.source,
64+
})
65+
})
66+
5167
// Set message handling via EventManager
5268
const chatHandler = new ChatMessageHandler(bot.username)
5369
bot.bot.on('chat', (username, message) => {
@@ -79,6 +95,9 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
7995
const perceptionPipeline = container.resolve('perceptionPipeline')
8096
perceptionPipeline.destroy()
8197

98+
const ruleEngine = container.resolve('ruleEngine')
99+
ruleEngine.destroy()
100+
82101
const reflexManager = container.resolve('reflexManager')
83102
reflexManager.destroy()
84103
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import type { TracedEvent } from './types'
2+
3+
import { useLogg } from '@guiiai/logg'
4+
import { describe, expect, it, vi } from 'vitest'
5+
6+
import { createEventBus } from './index'
7+
8+
describe('eventBus', () => {
9+
const createTestBus = () =>
10+
createEventBus({
11+
logger: useLogg('test'),
12+
config: { historySize: 100 },
13+
})
14+
15+
describe('emit', () => {
16+
it('should create an event with auto-generated id and timestamp', () => {
17+
const bus = createTestBus()
18+
19+
const event = bus.emit({
20+
type: 'test:event',
21+
payload: { foo: 'bar' },
22+
traceId: 'trace-1',
23+
source: { component: 'test' },
24+
})
25+
26+
expect(event.id).toBeDefined()
27+
expect(event.id.length).toBe(12)
28+
expect(event.traceId).toBe('trace-1')
29+
expect(event.type).toBe('test:event')
30+
expect(event.payload).toEqual({ foo: 'bar' })
31+
expect(event.timestamp).toBeGreaterThan(0)
32+
})
33+
34+
it('should generate traceId if not provided', () => {
35+
const bus = createTestBus()
36+
37+
const event = bus.emit({
38+
type: 'test:event',
39+
payload: {},
40+
source: { component: 'test' },
41+
})
42+
43+
expect(event.traceId).toBeDefined()
44+
expect(event.traceId.length).toBe(16)
45+
})
46+
47+
it('should freeze the event (immutable)', () => {
48+
const bus = createTestBus()
49+
50+
const event = bus.emit({
51+
type: 'test:event',
52+
payload: { mutable: 'data' },
53+
source: { component: 'test' },
54+
})
55+
56+
expect(Object.isFrozen(event)).toBe(true)
57+
expect(Object.isFrozen(event.payload)).toBe(true)
58+
expect(Object.isFrozen(event.source)).toBe(true)
59+
})
60+
61+
it('should deep freeze nested objects in payload', () => {
62+
const bus = createTestBus()
63+
64+
const event = bus.emit({
65+
type: 'test:event',
66+
payload: {
67+
level1: {
68+
level2: {
69+
value: 42,
70+
},
71+
},
72+
array: [{ item: 1 }, { item: 2 }],
73+
},
74+
source: { component: 'test' },
75+
})
76+
77+
expect(Object.isFrozen(event.payload)).toBe(true)
78+
expect(Object.isFrozen((event.payload as any).level1)).toBe(true)
79+
expect(Object.isFrozen((event.payload as any).level1.level2)).toBe(true)
80+
expect(Object.isFrozen((event.payload as any).array)).toBe(true)
81+
expect(Object.isFrozen((event.payload as any).array[0])).toBe(true)
82+
})
83+
})
84+
85+
describe('emitChild', () => {
86+
it('should inherit traceId and set parentId', () => {
87+
const bus = createTestBus()
88+
89+
const parent = bus.emit({
90+
type: 'parent:event',
91+
payload: {},
92+
source: { component: 'test' },
93+
})
94+
95+
const child = bus.emitChild(parent, {
96+
type: 'child:event',
97+
payload: { derived: true },
98+
source: { component: 'test' },
99+
})
100+
101+
expect(child.traceId).toBe(parent.traceId)
102+
expect(child.parentId).toBe(parent.id)
103+
})
104+
})
105+
106+
describe('subscribe', () => {
107+
it('should call handler for matching events', () => {
108+
const bus = createTestBus()
109+
const handler = vi.fn()
110+
111+
bus.subscribe('test:event', handler)
112+
bus.emit({
113+
type: 'test:event',
114+
payload: { data: 123 },
115+
source: { component: 'test' },
116+
})
117+
118+
expect(handler).toHaveBeenCalledTimes(1)
119+
expect(handler.mock.calls[0][0].payload).toEqual({ data: 123 })
120+
})
121+
122+
it('should support wildcard patterns', () => {
123+
const bus = createTestBus()
124+
const handler = vi.fn()
125+
126+
bus.subscribe('raw:*', handler)
127+
128+
bus.emit({
129+
type: 'raw:sighted:punch',
130+
payload: {},
131+
source: { component: 'test' },
132+
})
133+
bus.emit({
134+
type: 'raw:heard:sound',
135+
payload: {},
136+
source: { component: 'test' },
137+
})
138+
bus.emit({
139+
type: 'signal:attention',
140+
payload: {},
141+
source: { component: 'test' },
142+
})
143+
144+
expect(handler).toHaveBeenCalledTimes(2)
145+
})
146+
147+
it('should return unsubscribe function', () => {
148+
const bus = createTestBus()
149+
const handler = vi.fn()
150+
151+
const unsub = bus.subscribe('test:*', handler)
152+
153+
bus.emit({
154+
type: 'test:one',
155+
payload: {},
156+
source: { component: 'test' },
157+
})
158+
expect(handler).toHaveBeenCalledTimes(1)
159+
160+
unsub()
161+
162+
bus.emit({
163+
type: 'test:two',
164+
payload: {},
165+
source: { component: 'test' },
166+
})
167+
expect(handler).toHaveBeenCalledTimes(1) // Still 1
168+
})
169+
})
170+
171+
describe('trace context propagation', () => {
172+
it('should propagate trace context in handlers', () => {
173+
const bus = createTestBus()
174+
let childEvent: TracedEvent | undefined
175+
176+
bus.subscribe('parent:event', () => {
177+
// Emit within handler - should inherit context
178+
childEvent = bus.emit({
179+
type: 'child:event',
180+
payload: {},
181+
source: { component: 'handler' },
182+
})
183+
})
184+
185+
const parent = bus.emit({
186+
type: 'parent:event',
187+
payload: {},
188+
source: { component: 'test' },
189+
})
190+
191+
expect(childEvent).toBeDefined()
192+
expect(childEvent!.traceId).toBe(parent.traceId)
193+
expect(childEvent!.parentId).toBe(parent.id)
194+
})
195+
})
196+
197+
describe('history', () => {
198+
it('should store events in history', () => {
199+
const bus = createTestBus()
200+
201+
bus.emit({ type: 'e1', payload: {}, source: { component: 'test' } })
202+
bus.emit({ type: 'e2', payload: {}, source: { component: 'test' } })
203+
bus.emit({ type: 'e3', payload: {}, source: { component: 'test' } })
204+
205+
const history = bus.getHistory()
206+
expect(history.length).toBe(3)
207+
expect(history[0].type).toBe('e1')
208+
expect(history[2].type).toBe('e3')
209+
})
210+
211+
it('should respect historySize limit (ring buffer)', () => {
212+
const bus = createEventBus({
213+
logger: useLogg('test'),
214+
config: { historySize: 3 },
215+
})
216+
217+
bus.emit({ type: 'e1', payload: {}, source: { component: 'test' } })
218+
bus.emit({ type: 'e2', payload: {}, source: { component: 'test' } })
219+
bus.emit({ type: 'e3', payload: {}, source: { component: 'test' } })
220+
bus.emit({ type: 'e4', payload: {}, source: { component: 'test' } })
221+
222+
const history = bus.getHistory()
223+
expect(history.length).toBe(3)
224+
// Oldest event (e1) should be evicted
225+
expect(history.map(e => e.type)).toEqual(['e2', 'e3', 'e4'])
226+
})
227+
})
228+
229+
describe('getEventsByTrace', () => {
230+
it('should filter events by traceId', () => {
231+
const bus = createTestBus()
232+
233+
const e1 = bus.emit({ type: 'a', payload: {}, source: { component: 'test' } })
234+
bus.emitChild(e1, { type: 'b', payload: {}, source: { component: 'test' } })
235+
bus.emit({ type: 'c', payload: {}, source: { component: 'test' } }) // Different trace
236+
237+
const trace = bus.getEventsByTrace(e1.traceId)
238+
expect(trace.length).toBe(2)
239+
expect(trace.map(e => e.type)).toEqual(['a', 'b'])
240+
})
241+
})
242+
})

0 commit comments

Comments
 (0)