refactor(minecraft): simplify EventBus by removing history and trace query features
Strips ring buffer history storage, getHistory(), getEventsByTrace(), and historySize config from EventBus. Removes logger dependency and default config object. EventBus now only handles emit/subscribe/dispatch without persistent event storage. Updates container to use zero-config createEventBus(). Moves event-bus from cognitive/os/ to cognitive/ directory. Update services/minecraft/codex-skills/minecraft-debug-mcp/references/mcp-surface.md Update services/minecraft/codex-skills/minecraft-debug-mcp/references/mcp-surface.md Update services/minecraft/codex-skills/minecraft-debug-mcp/SKILL.md
This commit is contained in:
@@ -56,7 +56,7 @@ The perception layer acts as the sensory input hub, collecting raw Mineflayer si
|
||||
|
||||
**Pipeline**:
|
||||
- Event definitions in `events/definitions/*` bind Mineflayer events to normalized raw events.
|
||||
- `EventRegistry` emits `raw:<modality>:<kind>` events to the Cognitive EventBus.
|
||||
- `EventRegistry` emits `raw:<modality>:<kind>` events to the cognitive event bus.
|
||||
- `RuleEngine` evaluates YAML rules and emits derived `signal:*` events consumed by Reflex/Conscious layers.
|
||||
|
||||
**Key files**:
|
||||
@@ -149,7 +149,7 @@ src/
|
||||
│ │ ├── action-registry.ts # Tool dispatch + schema validation
|
||||
│ │ ├── llm-actions.ts # Tool catalog
|
||||
│ │ └── types.ts
|
||||
│ ├── os/ # EventBus + tracing core
|
||||
│ ├── event-bus.ts # Event bus core
|
||||
│ ├── container.ts # Dependency injection wiring
|
||||
│ ├── index.ts # Cognitive system entrypoint
|
||||
│ └── types.ts # Shared cognitive types
|
||||
|
||||
@@ -11,7 +11,7 @@ Use this skill to run the local bot and interact with its MCP debug interface sa
|
||||
|
||||
## Quick Start Workflow
|
||||
|
||||
1. Run `pnpm dev` from `/Users/rinshinohara/Repo/airi/services/minecraft` and keep it running.
|
||||
1. Run `pnpm dev` from `/path/to/project/root/services/minecraft` and keep it running.
|
||||
2. Wait for `MCP REPL server running at http://localhost:3001` in logs.
|
||||
3. Connect MCP client to `http://localhost:3001/sse`.
|
||||
4. Verify readiness with a read-only call:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Minecraft Debug MCP Surface
|
||||
|
||||
Implementation source: `/Users/rinshinohara/Repo/airi/services/minecraft/src/debug/mcp-repl-server.ts`.
|
||||
Implementation source: `/path/to/project/root/services/minecraft/src/debug/mcp-repl-server.ts`.
|
||||
|
||||
## Endpoint
|
||||
|
||||
@@ -9,7 +9,7 @@ Implementation source: `/Users/rinshinohara/Repo/airi/services/minecraft/src/deb
|
||||
- SSE fallback endpoint: `GET /sse` + `POST /messages`
|
||||
|
||||
The bot starts this server during normal runtime from:
|
||||
- `/Users/rinshinohara/Repo/airi/services/minecraft/src/cognitive/index.ts`
|
||||
- `/path/to/project/root/services/minecraft/src/cognitive/index.ts`
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Message } from '@xsai/shared-chat'
|
||||
import type { Action } from '../../libs/mineflayer/action'
|
||||
import type { TaskExecutor } from '../action/task-executor'
|
||||
import type { ActionInstruction } from '../action/types'
|
||||
import type { EventBus, TracedEvent } from '../os'
|
||||
import type { EventBus, TracedEvent } from '../event-bus'
|
||||
import type { PerceptionSignal } from '../perception/types/signals'
|
||||
import type { ReflexManager } from '../reflex/reflex-manager'
|
||||
import type { BotEvent, MineflayerWithAgents } from '../types'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Logg } from '@guiiai/logg'
|
||||
|
||||
import type { EventBus } from './os'
|
||||
import type { EventBus } from './event-bus'
|
||||
import type { RuleEngine } from './perception/rules'
|
||||
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
@@ -10,7 +10,7 @@ import { config } from '../composables/config'
|
||||
import { TaskExecutor } from './action/task-executor'
|
||||
import { Brain } from './conscious/brain'
|
||||
import { LLMAgent } from './conscious/llm-agent'
|
||||
import { createEventBus } from './os'
|
||||
import { createEventBus } from './event-bus'
|
||||
import { PerceptionPipeline } from './perception/pipeline'
|
||||
import { createRuleEngine } from './perception/rules'
|
||||
import { ReflexManager } from './reflex/reflex-manager'
|
||||
@@ -44,13 +44,8 @@ export function createAgentContainer() {
|
||||
model: config.openai.model,
|
||||
})).singleton(),
|
||||
|
||||
// Register EventBus (Cognitive OS core)
|
||||
eventBus: asFunction(() =>
|
||||
createEventBus({
|
||||
logger: useLogg('eventBus').useGlobalConfig(),
|
||||
config: { historySize: 10000 },
|
||||
}),
|
||||
).singleton(),
|
||||
// Register EventBus (cognitive event core)
|
||||
eventBus: asFunction(() => createEventBus()).singleton(),
|
||||
|
||||
// Register RuleEngine (YAML rules processing)
|
||||
ruleEngine: asFunction(({ eventBus }) => {
|
||||
|
||||
+4
-56
@@ -1,16 +1,11 @@
|
||||
import type { TracedEvent } from './types'
|
||||
import type { TracedEvent } from './event-bus'
|
||||
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { createEventBus } from './index'
|
||||
import { createEventBus } from './event-bus'
|
||||
|
||||
describe('eventBus', () => {
|
||||
const createTestBus = () =>
|
||||
createEventBus({
|
||||
logger: useLogg('test'),
|
||||
config: { historySize: 100 },
|
||||
})
|
||||
const createTestBus = () => createEventBus()
|
||||
|
||||
describe('emit', () => {
|
||||
it('should create an event with auto-generated id and timestamp', () => {
|
||||
@@ -164,7 +159,7 @@ describe('eventBus', () => {
|
||||
payload: {},
|
||||
source: { component: 'test' },
|
||||
})
|
||||
expect(handler).toHaveBeenCalledTimes(1) // Still 1
|
||||
expect(handler).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -174,7 +169,6 @@ describe('eventBus', () => {
|
||||
let childEvent: TracedEvent | undefined
|
||||
|
||||
bus.subscribe('parent:event', () => {
|
||||
// Emit within handler - should inherit context
|
||||
childEvent = bus.emit({
|
||||
type: 'child:event',
|
||||
payload: {},
|
||||
@@ -193,50 +187,4 @@ describe('eventBus', () => {
|
||||
expect(childEvent!.parentId).toBe(parent.id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('history', () => {
|
||||
it('should store events in history', () => {
|
||||
const bus = createTestBus()
|
||||
|
||||
bus.emit({ type: 'e1', payload: {}, source: { component: 'test' } })
|
||||
bus.emit({ type: 'e2', payload: {}, source: { component: 'test' } })
|
||||
bus.emit({ type: 'e3', payload: {}, source: { component: 'test' } })
|
||||
|
||||
const history = bus.getHistory()
|
||||
expect(history.length).toBe(3)
|
||||
expect(history[0].type).toBe('e1')
|
||||
expect(history[2].type).toBe('e3')
|
||||
})
|
||||
|
||||
it('should respect historySize limit (ring buffer)', () => {
|
||||
const bus = createEventBus({
|
||||
logger: useLogg('test'),
|
||||
config: { historySize: 3 },
|
||||
})
|
||||
|
||||
bus.emit({ type: 'e1', payload: {}, source: { component: 'test' } })
|
||||
bus.emit({ type: 'e2', payload: {}, source: { component: 'test' } })
|
||||
bus.emit({ type: 'e3', payload: {}, source: { component: 'test' } })
|
||||
bus.emit({ type: 'e4', payload: {}, source: { component: 'test' } })
|
||||
|
||||
const history = bus.getHistory()
|
||||
expect(history.length).toBe(3)
|
||||
// Oldest event (e1) should be evicted
|
||||
expect(history.map(e => e.type)).toEqual(['e2', 'e3', 'e4'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEventsByTrace', () => {
|
||||
it('should filter events by traceId', () => {
|
||||
const bus = createTestBus()
|
||||
|
||||
const e1 = bus.emit({ type: 'a', payload: {}, source: { component: 'test' } })
|
||||
bus.emitChild(e1, { type: 'b', payload: {}, source: { component: 'test' } })
|
||||
bus.emit({ type: 'c', payload: {}, source: { component: 'test' } }) // Different trace
|
||||
|
||||
const trace = bus.getEventsByTrace(e1.traceId)
|
||||
expect(trace.length).toBe(2)
|
||||
expect(trace.map(e => e.type)).toEqual(['a', 'b'])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,175 @@
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
export type EventId = string
|
||||
export type TraceId = string
|
||||
|
||||
export interface EventSource {
|
||||
readonly component: string
|
||||
readonly id?: string
|
||||
}
|
||||
|
||||
export interface TracedEvent<T = unknown> {
|
||||
readonly id: EventId
|
||||
readonly traceId: TraceId
|
||||
readonly parentId?: EventId
|
||||
readonly type: string
|
||||
readonly payload: Readonly<T>
|
||||
readonly timestamp: number
|
||||
readonly source: EventSource
|
||||
}
|
||||
|
||||
export interface EventInput<T = unknown> {
|
||||
readonly type: string
|
||||
readonly payload: Readonly<T>
|
||||
readonly source: EventSource
|
||||
readonly traceId?: string
|
||||
readonly parentId?: string
|
||||
}
|
||||
|
||||
export type EventHandler<T = unknown> = (event: TracedEvent<T>) => void
|
||||
export type Unsubscribe = () => void
|
||||
export type EventPattern = string
|
||||
|
||||
interface TraceContext {
|
||||
traceId: string
|
||||
parentId?: string
|
||||
}
|
||||
|
||||
interface Subscription {
|
||||
pattern: EventPattern
|
||||
handler: EventHandler
|
||||
}
|
||||
|
||||
const traceStorage = new AsyncLocalStorage<TraceContext>()
|
||||
|
||||
function generateEventId(): string {
|
||||
return nanoid(12)
|
||||
}
|
||||
|
||||
function generateTraceId(): string {
|
||||
return nanoid(16)
|
||||
}
|
||||
|
||||
function matchesPattern(pattern: EventPattern, eventType: string): boolean {
|
||||
if (pattern === '*')
|
||||
return true
|
||||
|
||||
if (pattern.endsWith(':*')) {
|
||||
const prefix = pattern.slice(0, -1)
|
||||
return eventType.startsWith(prefix)
|
||||
}
|
||||
|
||||
return pattern === eventType
|
||||
}
|
||||
|
||||
function deepFreeze<T>(value: T): T {
|
||||
if (value === null || typeof value !== 'object' || Object.isFrozen(value))
|
||||
return value
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value)
|
||||
deepFreeze(item)
|
||||
return Object.freeze(value)
|
||||
}
|
||||
|
||||
for (const child of Object.values(value as Record<string, unknown>))
|
||||
deepFreeze(child)
|
||||
|
||||
return Object.freeze(value)
|
||||
}
|
||||
|
||||
function resolveTraceContext(input: Pick<EventInput, 'traceId' | 'parentId'>): TraceContext {
|
||||
if (input.traceId) {
|
||||
return Object.freeze({
|
||||
traceId: input.traceId,
|
||||
parentId: input.parentId,
|
||||
})
|
||||
}
|
||||
|
||||
const inherited = traceStorage.getStore()
|
||||
if (inherited) {
|
||||
return Object.freeze({
|
||||
traceId: inherited.traceId,
|
||||
parentId: inherited.parentId,
|
||||
})
|
||||
}
|
||||
|
||||
return Object.freeze({ traceId: generateTraceId() })
|
||||
}
|
||||
|
||||
function withTraceContext<T>(traceId: string, parentId: string, fn: () => T): T {
|
||||
return traceStorage.run({ traceId, parentId }, fn)
|
||||
}
|
||||
|
||||
export class EventBus {
|
||||
private readonly subscriptions = new Map<number, Subscription>()
|
||||
private nextSubId = 0
|
||||
|
||||
public emit<T>(input: EventInput<T>): TracedEvent<T> {
|
||||
const trace = resolveTraceContext({
|
||||
traceId: input.traceId,
|
||||
parentId: input.parentId,
|
||||
})
|
||||
|
||||
const event = deepFreeze({
|
||||
id: generateEventId(),
|
||||
traceId: trace.traceId,
|
||||
parentId: trace.parentId,
|
||||
type: input.type,
|
||||
payload: input.payload,
|
||||
timestamp: Date.now(),
|
||||
source: input.source,
|
||||
} satisfies TracedEvent<T>)
|
||||
|
||||
this.dispatch(event)
|
||||
return event
|
||||
}
|
||||
|
||||
public emitChild<T>(
|
||||
parent: TracedEvent,
|
||||
input: Omit<EventInput<T>, 'traceId' | 'parentId'>,
|
||||
): TracedEvent<T> {
|
||||
return this.emit({
|
||||
...input,
|
||||
traceId: parent.traceId,
|
||||
parentId: parent.id,
|
||||
})
|
||||
}
|
||||
|
||||
public subscribe<T = unknown>(
|
||||
pattern: EventPattern,
|
||||
handler: EventHandler<T>,
|
||||
): Unsubscribe {
|
||||
const id = this.nextSubId++
|
||||
this.subscriptions.set(id, {
|
||||
pattern,
|
||||
handler: handler as EventHandler,
|
||||
})
|
||||
|
||||
return () => {
|
||||
this.subscriptions.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
private dispatch(event: TracedEvent): void {
|
||||
for (const sub of this.subscriptions.values()) {
|
||||
if (!matchesPattern(sub.pattern, event.type))
|
||||
continue
|
||||
|
||||
try {
|
||||
withTraceContext(event.traceId, event.id, () => {
|
||||
sub.handler(event)
|
||||
})
|
||||
}
|
||||
catch {
|
||||
// Keep dispatch resilient by isolating subscriber failures.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createEventBus(): EventBus {
|
||||
return new EventBus()
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
import type { Logg } from '@guiiai/logg'
|
||||
|
||||
import type {
|
||||
EventBusConfig,
|
||||
EventBusSnapshot,
|
||||
EventHandler,
|
||||
EventInput,
|
||||
EventPattern,
|
||||
Subscription,
|
||||
TraceContext,
|
||||
TracedEvent,
|
||||
Unsubscribe,
|
||||
} from './types'
|
||||
|
||||
import {
|
||||
deriveTraceContext,
|
||||
generateEventId,
|
||||
resolveTraceContext,
|
||||
runWithTraceContext,
|
||||
} from './tracer'
|
||||
import { freezeEvent } from './types'
|
||||
|
||||
/**
|
||||
* Default EventBus configuration
|
||||
*/
|
||||
const DEFAULT_CONFIG: EventBusConfig = Object.freeze({
|
||||
historySize: 10000,
|
||||
})
|
||||
|
||||
/**
|
||||
* Check if an event type matches a pattern
|
||||
* Supports wildcards: 'raw:*' matches 'raw:sighted:punch'
|
||||
*/
|
||||
function matchesPattern(pattern: EventPattern, eventType: string): boolean {
|
||||
if (pattern === '*')
|
||||
return true
|
||||
|
||||
if (pattern.endsWith(':*')) {
|
||||
const prefix = pattern.slice(0, -1) // Remove the '*'
|
||||
return eventType.startsWith(prefix)
|
||||
}
|
||||
|
||||
return pattern === eventType
|
||||
}
|
||||
|
||||
/**
|
||||
* EventBus - The heart of the Cognitive OS
|
||||
*
|
||||
* This is the ONLY component with mutable internal state.
|
||||
* All other components should be pure functions that interact
|
||||
* through the EventBus.
|
||||
*
|
||||
* Design principles:
|
||||
* - Events are immutable once created
|
||||
* - Trace context automatically propagates through handlers
|
||||
* - Ring buffer prevents memory leaks
|
||||
* - Pattern-based subscriptions for flexible routing
|
||||
*/
|
||||
export class EventBus {
|
||||
// Internal mutable state - isolated from the outside world
|
||||
private readonly buffer: (TracedEvent | null)[] = []
|
||||
private readonly subscriptions = new Map<number, Subscription>()
|
||||
private nextSubId = 0
|
||||
private writeIndex = 0 // Next position to write
|
||||
private count = 0 // Number of events stored
|
||||
|
||||
constructor(
|
||||
private readonly deps: {
|
||||
logger: Logg
|
||||
config?: Partial<EventBusConfig>
|
||||
},
|
||||
) {
|
||||
// Pre-allocate buffer
|
||||
const size = this.config.historySize
|
||||
this.buffer = new Array(size).fill(null)
|
||||
}
|
||||
|
||||
private get config(): EventBusConfig {
|
||||
return { ...DEFAULT_CONFIG, ...this.deps.config }
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event to the bus
|
||||
*
|
||||
* This is the ONLY side effect entry point.
|
||||
* Returns the created event (immutable).
|
||||
*/
|
||||
public emit<T>(input: EventInput<T>): TracedEvent<T> {
|
||||
// Resolve trace context (from explicit, async context, or new)
|
||||
const trace = resolveTraceContext({
|
||||
traceId: input.traceId,
|
||||
parentId: input.parentId,
|
||||
})
|
||||
|
||||
// Create the full event
|
||||
const event = freezeEvent<T>({
|
||||
id: generateEventId(),
|
||||
traceId: trace.traceId,
|
||||
parentId: trace.parentId,
|
||||
type: input.type,
|
||||
payload: input.payload,
|
||||
timestamp: Date.now(),
|
||||
source: input.source,
|
||||
})
|
||||
|
||||
// Store in ring buffer
|
||||
this.storeEvent(event)
|
||||
|
||||
// Dispatch to subscribers
|
||||
this.dispatch(event)
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event as a child of another event
|
||||
* Automatically sets up trace context
|
||||
*/
|
||||
public emitChild<T>(
|
||||
parent: TracedEvent,
|
||||
input: Omit<EventInput<T>, 'traceId' | 'parentId'>,
|
||||
): TracedEvent<T> {
|
||||
return this.emit({
|
||||
...input,
|
||||
traceId: parent.traceId,
|
||||
parentId: parent.id,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to events matching a pattern
|
||||
* Returns an unsubscribe function
|
||||
*/
|
||||
public subscribe<T = unknown>(
|
||||
pattern: EventPattern,
|
||||
handler: EventHandler<T>,
|
||||
): Unsubscribe {
|
||||
const id = this.nextSubId++
|
||||
this.subscriptions.set(id, {
|
||||
pattern,
|
||||
handler: handler as EventHandler,
|
||||
})
|
||||
|
||||
return () => {
|
||||
this.subscriptions.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event history as an immutable array
|
||||
* Events are returned in chronological order (oldest first)
|
||||
*/
|
||||
public getHistory(): readonly TracedEvent[] {
|
||||
const size = this.config.historySize
|
||||
|
||||
if (this.count === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const result: TracedEvent[] = []
|
||||
|
||||
// Calculate start position (oldest event)
|
||||
// If buffer is full, oldest is at writeIndex
|
||||
// If not full, oldest is at 0
|
||||
const startIdx = this.count < size ? 0 : this.writeIndex
|
||||
|
||||
for (let i = 0; i < this.count; i++) {
|
||||
const idx = (startIdx + i) % size
|
||||
const event = this.buffer[idx]
|
||||
if (event) {
|
||||
result.push(event)
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get debug snapshot
|
||||
*/
|
||||
public getSnapshot(): EventBusSnapshot {
|
||||
return Object.freeze({
|
||||
events: this.getHistory(),
|
||||
subscriptionCount: this.subscriptions.size,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Replay a sequence of events
|
||||
* Used for debugging and testing
|
||||
*/
|
||||
public replay(events: readonly TracedEvent[]): void {
|
||||
this.deps.logger.withFields({ count: events.length }).log('EventBus: replaying events')
|
||||
|
||||
for (const event of events) {
|
||||
// Store without re-generating IDs
|
||||
this.storeEvent(event)
|
||||
// Dispatch to current subscribers
|
||||
this.dispatch(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all events (for testing)
|
||||
*/
|
||||
public clear(): void {
|
||||
this.buffer.fill(null)
|
||||
this.writeIndex = 0
|
||||
this.count = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events by trace ID
|
||||
*/
|
||||
public getEventsByTrace(traceId: string): readonly TracedEvent[] {
|
||||
return Object.freeze(
|
||||
this.getHistory().filter(e => e.traceId === traceId),
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Private methods
|
||||
// ============================================================
|
||||
|
||||
private storeEvent(event: TracedEvent): void {
|
||||
const size = this.config.historySize
|
||||
|
||||
// Write to current position
|
||||
this.buffer[this.writeIndex] = event
|
||||
|
||||
// Advance write position
|
||||
this.writeIndex = (this.writeIndex + 1) % size
|
||||
|
||||
// Update count (max is buffer size)
|
||||
if (this.count < size) {
|
||||
this.count++
|
||||
}
|
||||
}
|
||||
|
||||
private dispatch(event: TracedEvent): void {
|
||||
// Create trace context for handlers
|
||||
const childContext: TraceContext = deriveTraceContext(event.traceId, event.id)
|
||||
|
||||
for (const sub of this.subscriptions.values()) {
|
||||
if (!matchesPattern(sub.pattern, event.type))
|
||||
continue
|
||||
|
||||
try {
|
||||
// Run handler within trace context so child emissions inherit it
|
||||
runWithTraceContext(childContext, () => {
|
||||
sub.handler(event)
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
this.deps.logger
|
||||
.withError(err as Error)
|
||||
.withFields({ eventType: event.type, pattern: sub.pattern })
|
||||
.error('EventBus: handler error')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EventBus instance
|
||||
* Factory function for cleaner API
|
||||
*/
|
||||
export function createEventBus(deps: {
|
||||
logger: Logg
|
||||
config?: Partial<EventBusConfig>
|
||||
}): EventBus {
|
||||
return new EventBus(deps)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Cognitive OS - Event-sourced architecture for the cognitive engine
|
||||
*
|
||||
* Core principles:
|
||||
* - All state changes go through TracedEvents
|
||||
* - Events are immutable
|
||||
* - Trace context propagates automatically
|
||||
* - EventBus is the only mutable container
|
||||
*/
|
||||
|
||||
// NOTE: RuleEngine and rule utilities moved to cognitive/perception/rules.
|
||||
|
||||
// EventBus
|
||||
export { createEventBus, EventBus } from './event-bus'
|
||||
|
||||
// Tracer utilities
|
||||
export {
|
||||
createTraceContext,
|
||||
deriveTraceContext,
|
||||
generateEventId,
|
||||
generateTraceId,
|
||||
getCurrentTraceContext,
|
||||
resolveTraceContext,
|
||||
runWithTraceContext,
|
||||
} from './tracer'
|
||||
|
||||
// Core types
|
||||
export type {
|
||||
EventBusConfig,
|
||||
EventBusSnapshot,
|
||||
EventHandler,
|
||||
EventId,
|
||||
EventInput,
|
||||
EventPattern,
|
||||
EventSource,
|
||||
Subscription,
|
||||
TraceContext,
|
||||
TracedEvent,
|
||||
TraceId,
|
||||
Unsubscribe,
|
||||
} from './types'
|
||||
|
||||
export { freezeEvent } from './types'
|
||||
@@ -1,98 +0,0 @@
|
||||
import type { EventId, TraceContext, TraceId } from './types'
|
||||
|
||||
/**
|
||||
* AsyncLocalStorage-based trace context propagation
|
||||
* This allows handlers to automatically inherit trace context
|
||||
*/
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
/**
|
||||
* Generate a unique event ID
|
||||
* Uses nanoid for compact, URL-safe IDs
|
||||
*/
|
||||
export function generateEventId(): EventId {
|
||||
return nanoid(12)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique trace ID
|
||||
*/
|
||||
export function generateTraceId(): TraceId {
|
||||
return nanoid(16)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new trace context for a fresh event chain
|
||||
*/
|
||||
export function createTraceContext(): TraceContext {
|
||||
return Object.freeze({
|
||||
traceId: generateTraceId(),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a child trace context from a parent event
|
||||
* Preserves the same traceId but sets the parentId
|
||||
*/
|
||||
export function deriveTraceContext(
|
||||
parentTraceId: TraceId,
|
||||
parentEventId: EventId,
|
||||
): TraceContext {
|
||||
return Object.freeze({
|
||||
traceId: parentTraceId,
|
||||
parentId: parentEventId,
|
||||
})
|
||||
}
|
||||
|
||||
const traceStorage = new AsyncLocalStorage<TraceContext>()
|
||||
|
||||
/**
|
||||
* Get the current trace context from async local storage
|
||||
* Returns undefined if not in a traced context
|
||||
*/
|
||||
export function getCurrentTraceContext(): TraceContext | undefined {
|
||||
return traceStorage.getStore()
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function within a trace context
|
||||
* All events emitted within this context will inherit the trace
|
||||
*/
|
||||
export function runWithTraceContext<T>(
|
||||
context: TraceContext,
|
||||
fn: () => T,
|
||||
): T {
|
||||
return traceStorage.run(context, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or derive trace context for an event
|
||||
* If we're in a traced context, derive from it; otherwise create new
|
||||
*/
|
||||
export function resolveTraceContext(
|
||||
explicit?: Partial<TraceContext>,
|
||||
): TraceContext {
|
||||
const current = getCurrentTraceContext()
|
||||
|
||||
if (explicit?.traceId) {
|
||||
// Explicit context provided
|
||||
return Object.freeze({
|
||||
traceId: explicit.traceId,
|
||||
parentId: explicit.parentId,
|
||||
})
|
||||
}
|
||||
|
||||
if (current) {
|
||||
// Derive from current async context
|
||||
// Note: parentId should be set by the caller who knows the parent event
|
||||
return Object.freeze({
|
||||
traceId: current.traceId,
|
||||
parentId: current.parentId,
|
||||
})
|
||||
}
|
||||
|
||||
// New trace
|
||||
return createTraceContext()
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Core types for the Cognitive OS event-sourced architecture.
|
||||
* All types are immutable by design (Readonly).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unique identifier for events and traces
|
||||
*/
|
||||
export type EventId = string
|
||||
export type TraceId = string
|
||||
|
||||
/**
|
||||
* Event source identifier
|
||||
*/
|
||||
export interface EventSource {
|
||||
readonly component: string
|
||||
readonly id?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A traced event - the core unit of the event-sourced system.
|
||||
* All fields are readonly to enforce immutability.
|
||||
*/
|
||||
export interface TracedEvent<T = unknown> {
|
||||
/** Unique event ID */
|
||||
readonly id: EventId
|
||||
/** Trace ID shared across related events */
|
||||
readonly traceId: TraceId
|
||||
/** Parent event ID (if derived from another event) */
|
||||
readonly parentId?: EventId
|
||||
/** Event type identifier (e.g. 'raw:sighted:arm_swing') */
|
||||
readonly type: string
|
||||
/** Event payload - should be immutable */
|
||||
readonly payload: Readonly<T>
|
||||
/** Event timestamp */
|
||||
readonly timestamp: number
|
||||
/** Source component */
|
||||
readonly source: EventSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for creating a new event
|
||||
* traceId and parentId are optional - will be auto-generated or inherited from context
|
||||
* id and timestamp are always auto-generated
|
||||
*/
|
||||
export interface EventInput<T = unknown> {
|
||||
readonly type: string
|
||||
readonly payload: Readonly<T>
|
||||
readonly source: EventSource
|
||||
readonly traceId?: string
|
||||
readonly parentId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler function - should be a pure function that may emit new events
|
||||
*/
|
||||
export type EventHandler<T = unknown> = (event: TracedEvent<T>) => void
|
||||
|
||||
/**
|
||||
* Unsubscribe function returned by subscribe
|
||||
*/
|
||||
export type Unsubscribe = () => void
|
||||
|
||||
/**
|
||||
* Event pattern for subscription filtering
|
||||
* Supports wildcards: 'raw:*' matches 'raw:sighted:punch'
|
||||
*/
|
||||
export type EventPattern = string
|
||||
|
||||
/**
|
||||
* Subscription record
|
||||
*/
|
||||
export interface Subscription {
|
||||
readonly pattern: EventPattern
|
||||
readonly handler: EventHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* EventBus configuration
|
||||
*/
|
||||
export interface EventBusConfig {
|
||||
/** Maximum events to keep in history (ring buffer) */
|
||||
readonly historySize: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of EventBus state for debugging
|
||||
*/
|
||||
export interface EventBusSnapshot {
|
||||
readonly events: readonly TracedEvent[]
|
||||
readonly subscriptionCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace context for propagating trace information
|
||||
*/
|
||||
export interface TraceContext {
|
||||
readonly traceId: TraceId
|
||||
readonly parentId?: EventId
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep freeze an object (recursively freeze all nested objects)
|
||||
* Performance note: Use sparingly on large objects
|
||||
*/
|
||||
export function deepFreeze<T>(obj: T): Readonly<T> {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Don't freeze already frozen objects
|
||||
if (Object.isFrozen(obj)) {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Freeze arrays
|
||||
if (Array.isArray(obj)) {
|
||||
obj.forEach(item => deepFreeze(item))
|
||||
return Object.freeze(obj) as Readonly<T>
|
||||
}
|
||||
|
||||
// Freeze object properties
|
||||
Object.keys(obj).forEach((key) => {
|
||||
const value = (obj as Record<string, unknown>)[key]
|
||||
if (value !== null && typeof value === 'object') {
|
||||
deepFreeze(value)
|
||||
}
|
||||
})
|
||||
|
||||
return Object.freeze(obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an immutable event by deep freezing all its properties
|
||||
* This ensures the entire object tree is immutable
|
||||
*/
|
||||
export function freezeEvent<T>(event: TracedEvent<T>): TracedEvent<T> {
|
||||
return deepFreeze(event)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Logg } from '@guiiai/logg'
|
||||
|
||||
import type { EventBus } from '../os'
|
||||
import type { EventBus } from '../event-bus'
|
||||
import type { MineflayerWithAgents } from '../types'
|
||||
|
||||
import { EventRegistry } from './events'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import type { Logg } from '@guiiai/logg'
|
||||
|
||||
import type { EventBus, TracedEvent } from '../../os'
|
||||
import type { EventBus, TracedEvent } from '../../event-bus'
|
||||
import type {
|
||||
AccumulatorsState,
|
||||
ParsedRule,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Logg } from '@guiiai/logg'
|
||||
|
||||
import type { TaskExecutor } from '../action/task-executor'
|
||||
import type { EventBus, TracedEvent } from '../os'
|
||||
import type { EventBus, TracedEvent } from '../event-bus'
|
||||
import type { PerceptionSignal } from '../perception/types/signals'
|
||||
import type { MineflayerWithAgents } from '../types'
|
||||
import type { ReflexContextState } from './context'
|
||||
|
||||
@@ -66,7 +66,7 @@ export interface ReflexStateEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Traced event from the Cognitive OS EventBus
|
||||
* Traced event from the cognitive event bus
|
||||
*/
|
||||
export interface TraceEvent {
|
||||
/** Unique event ID */
|
||||
|
||||
Reference in New Issue
Block a user