@agentxjs/core 1.9.1-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +31 -0
- package/src/agent/AgentStateMachine.ts +151 -0
- package/src/agent/README.md +296 -0
- package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
- package/src/agent/__tests__/createAgent.test.ts +728 -0
- package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
- package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
- package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
- package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
- package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
- package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
- package/src/agent/createAgent.ts +467 -0
- package/src/agent/engine/AgentProcessor.ts +106 -0
- package/src/agent/engine/MealyMachine.ts +184 -0
- package/src/agent/engine/internal/index.ts +35 -0
- package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
- package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
- package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
- package/src/agent/engine/mealy/Mealy.ts +308 -0
- package/src/agent/engine/mealy/Processor.ts +70 -0
- package/src/agent/engine/mealy/Sink.ts +56 -0
- package/src/agent/engine/mealy/Source.ts +51 -0
- package/src/agent/engine/mealy/Store.ts +98 -0
- package/src/agent/engine/mealy/combinators.ts +176 -0
- package/src/agent/engine/mealy/index.ts +45 -0
- package/src/agent/index.ts +106 -0
- package/src/agent/types/engine.ts +395 -0
- package/src/agent/types/event.ts +478 -0
- package/src/agent/types/index.ts +197 -0
- package/src/agent/types/message.ts +387 -0
- package/src/common/index.ts +8 -0
- package/src/common/logger/ConsoleLogger.ts +137 -0
- package/src/common/logger/LoggerFactoryImpl.ts +123 -0
- package/src/common/logger/index.ts +26 -0
- package/src/common/logger/types.ts +98 -0
- package/src/container/Container.ts +185 -0
- package/src/container/index.ts +44 -0
- package/src/container/types.ts +71 -0
- package/src/driver/index.ts +42 -0
- package/src/driver/types.ts +363 -0
- package/src/event/EventBus.ts +260 -0
- package/src/event/README.md +237 -0
- package/src/event/__tests__/EventBus.test.ts +251 -0
- package/src/event/index.ts +46 -0
- package/src/event/types/agent.ts +512 -0
- package/src/event/types/base.ts +241 -0
- package/src/event/types/bus.ts +429 -0
- package/src/event/types/command.ts +749 -0
- package/src/event/types/container.ts +471 -0
- package/src/event/types/driver.ts +452 -0
- package/src/event/types/index.ts +26 -0
- package/src/event/types/session.ts +314 -0
- package/src/image/Image.ts +203 -0
- package/src/image/index.ts +36 -0
- package/src/image/types.ts +77 -0
- package/src/index.ts +20 -0
- package/src/mq/OffsetGenerator.ts +48 -0
- package/src/mq/README.md +166 -0
- package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
- package/src/mq/index.ts +18 -0
- package/src/mq/types.ts +172 -0
- package/src/network/RpcClient.ts +455 -0
- package/src/network/index.ts +76 -0
- package/src/network/jsonrpc.ts +336 -0
- package/src/network/protocol.ts +90 -0
- package/src/network/types.ts +284 -0
- package/src/persistence/index.ts +27 -0
- package/src/persistence/types.ts +226 -0
- package/src/runtime/AgentXRuntime.ts +501 -0
- package/src/runtime/index.ts +56 -0
- package/src/runtime/types.ts +236 -0
- package/src/session/Session.ts +71 -0
- package/src/session/index.ts +25 -0
- package/src/session/types.ts +77 -0
- package/src/workspace/index.ts +27 -0
- package/src/workspace/types.ts +131 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# @agentxjs/core/event
|
|
2
|
+
|
|
3
|
+
Event System for AgentX - Pure memory EventBus with typed events.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The event module provides a central event bus for component communication within a process. It's a pure in-memory pub/sub system using RxJS, with no platform dependencies.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ EventBus │
|
|
12
|
+
│ │
|
|
13
|
+
│ Producer ──emit()──► Subject ──dispatch()──► Consumers │
|
|
14
|
+
│ │ │
|
|
15
|
+
│ ┌──────┴──────┐ │
|
|
16
|
+
│ │ Filter │ │
|
|
17
|
+
│ │ Priority │ │
|
|
18
|
+
│ │ Once │ │
|
|
19
|
+
│ └─────────────┘ │
|
|
20
|
+
└─────────────────────────────────────────────────────────────┘
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Key Concepts
|
|
24
|
+
|
|
25
|
+
### Event-Driven Architecture
|
|
26
|
+
|
|
27
|
+
All components communicate through typed events:
|
|
28
|
+
|
|
29
|
+
| Source | Events | Examples |
|
|
30
|
+
| ------------- | ----------------- | ------------------------------------------ |
|
|
31
|
+
| `environment` | LLM API responses | `message_start`, `text_delta`, `tool_call` |
|
|
32
|
+
| `agent` | Agent internal | `conversation_start`, `assistant_message` |
|
|
33
|
+
| `session` | Session lifecycle | `session_created`, `session_saved` |
|
|
34
|
+
| `container` | Container ops | `container_created`, `agent_registered` |
|
|
35
|
+
| `command` | Request/Response | `container_create_request/response` |
|
|
36
|
+
|
|
37
|
+
### Producer/Consumer Pattern
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Producer - can only emit events
|
|
41
|
+
class ClaudeReceptor {
|
|
42
|
+
constructor(private producer: EventProducer) {}
|
|
43
|
+
|
|
44
|
+
onChunk(text: string) {
|
|
45
|
+
this.producer.emit({
|
|
46
|
+
type: "text_delta",
|
|
47
|
+
timestamp: Date.now(),
|
|
48
|
+
data: { text },
|
|
49
|
+
source: "environment",
|
|
50
|
+
category: "stream",
|
|
51
|
+
intent: "notification",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Consumer - can only subscribe to events
|
|
57
|
+
class BusDriver {
|
|
58
|
+
constructor(consumer: EventConsumer) {
|
|
59
|
+
consumer.on("text_delta", (e) => {
|
|
60
|
+
console.log("Received:", e.data.text);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Basic Usage
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { EventBus } from "@agentxjs/core/event";
|
|
72
|
+
|
|
73
|
+
const bus = new EventBus();
|
|
74
|
+
|
|
75
|
+
// Subscribe to specific event type
|
|
76
|
+
bus.on("text_delta", (event) => {
|
|
77
|
+
console.log("Text:", event.data.text);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Subscribe to all events
|
|
81
|
+
bus.onAny((event) => {
|
|
82
|
+
console.log("Event:", event.type);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Emit event
|
|
86
|
+
bus.emit({
|
|
87
|
+
type: "text_delta",
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
data: { text: "Hello!" },
|
|
90
|
+
source: "environment",
|
|
91
|
+
category: "stream",
|
|
92
|
+
intent: "notification",
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Subscription Options
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Filter events
|
|
100
|
+
bus.on("text_delta", handler, {
|
|
101
|
+
filter: (e) => e.context?.agentId === "agent-1",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Priority (higher runs first)
|
|
105
|
+
bus.on("text_delta", criticalHandler, { priority: 10 });
|
|
106
|
+
bus.on("text_delta", normalHandler, { priority: 0 });
|
|
107
|
+
|
|
108
|
+
// One-time subscription
|
|
109
|
+
bus.once("message_stop", (e) => {
|
|
110
|
+
console.log("Stream ended");
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Command Request/Response
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Send request and wait for response
|
|
118
|
+
const response = await bus.request(
|
|
119
|
+
"container_create_request",
|
|
120
|
+
{
|
|
121
|
+
containerId: "my-container",
|
|
122
|
+
},
|
|
123
|
+
30000
|
|
124
|
+
); // 30s timeout
|
|
125
|
+
|
|
126
|
+
console.log("Created:", response.data.containerId);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Restricted Views
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const bus = new EventBus();
|
|
133
|
+
|
|
134
|
+
// Give producer to components that emit
|
|
135
|
+
const receptor = new ClaudeReceptor(bus.asProducer());
|
|
136
|
+
|
|
137
|
+
// Give consumer to components that subscribe
|
|
138
|
+
const driver = new BusDriver(bus.asConsumer());
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Architecture
|
|
142
|
+
|
|
143
|
+
### Directory Structure
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
event/
|
|
147
|
+
├── types/
|
|
148
|
+
│ ├── index.ts # Re-exports all types
|
|
149
|
+
│ ├── base.ts # SystemEvent, EventSource, EventContext
|
|
150
|
+
│ ├── bus.ts # EventBus, EventProducer, EventConsumer
|
|
151
|
+
│ ├── agent.ts # Agent stream/state/message/turn events
|
|
152
|
+
│ ├── session.ts # Session lifecycle/persist/action events
|
|
153
|
+
│ ├── container.ts # Container lifecycle/sandbox events
|
|
154
|
+
│ ├── command.ts # Request/Response events + CommandEventMap
|
|
155
|
+
│ └── environment.ts # DriveableEvent, ConnectionEvent, StopReason
|
|
156
|
+
│
|
|
157
|
+
├── EventBus.ts # RxJS-based implementation
|
|
158
|
+
├── __tests__/
|
|
159
|
+
└── index.ts
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Type Hierarchy
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
BusEvent (simple)
|
|
166
|
+
└── { type, timestamp, data }
|
|
167
|
+
|
|
168
|
+
SystemEvent (full)
|
|
169
|
+
└── { type, timestamp, data, source, category, intent, context?, broadcastable? }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API Reference
|
|
173
|
+
|
|
174
|
+
### EventBus
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
class EventBus implements EventBusInterface {
|
|
178
|
+
// Emit
|
|
179
|
+
emit(event: BusEvent): void;
|
|
180
|
+
emitBatch(events: BusEvent[]): void;
|
|
181
|
+
emitCommand<T>(type: T, data: CommandEventMap[T]["data"]): void;
|
|
182
|
+
|
|
183
|
+
// Subscribe
|
|
184
|
+
on(type: string, handler: BusEventHandler, options?: SubscribeOptions): Unsubscribe;
|
|
185
|
+
on(types: string[], handler: BusEventHandler, options?: SubscribeOptions): Unsubscribe;
|
|
186
|
+
onAny(handler: BusEventHandler, options?: SubscribeOptions): Unsubscribe;
|
|
187
|
+
once(type: string, handler: BusEventHandler): Unsubscribe;
|
|
188
|
+
onCommand<T>(type: T, handler: (event: CommandEventMap[T]) => void): Unsubscribe;
|
|
189
|
+
|
|
190
|
+
// Request/Response
|
|
191
|
+
request<T>(type: T, data: RequestDataFor<T>, timeout?: number): Promise<ResponseEventFor<T>>;
|
|
192
|
+
|
|
193
|
+
// Views
|
|
194
|
+
asProducer(): EventProducer;
|
|
195
|
+
asConsumer(): EventConsumer;
|
|
196
|
+
|
|
197
|
+
// Lifecycle
|
|
198
|
+
destroy(): void;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### SubscribeOptions
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
interface SubscribeOptions<E> {
|
|
206
|
+
filter?: (event: E) => boolean; // Filter events
|
|
207
|
+
priority?: number; // Execution order (higher first)
|
|
208
|
+
once?: boolean; // Auto-unsubscribe after first
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Platform Independence
|
|
213
|
+
|
|
214
|
+
This module has **zero platform dependencies**:
|
|
215
|
+
|
|
216
|
+
- No `node:*` imports
|
|
217
|
+
- No `bun:*` imports
|
|
218
|
+
- Only depends on `rxjs` (pure JavaScript)
|
|
219
|
+
|
|
220
|
+
Can run in: Node.js, Bun, Deno, Cloudflare Workers, Browser.
|
|
221
|
+
|
|
222
|
+
## Not a Message Queue
|
|
223
|
+
|
|
224
|
+
EventBus is for **in-process** communication only:
|
|
225
|
+
|
|
226
|
+
| | EventBus | MessageQueue |
|
|
227
|
+
| ----------- | -------------------- | ----------------- |
|
|
228
|
+
| Scope | Process-internal | Cross-process |
|
|
229
|
+
| Persistence | None | SQLite/Redis |
|
|
230
|
+
| Delivery | Best-effort | At-least-once |
|
|
231
|
+
| Use case | Component decoupling | Reliable delivery |
|
|
232
|
+
|
|
233
|
+
For cross-process or persistent messaging, use the `mq` module.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
MIT
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBus.test.ts - Unit tests for EventBus implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from "bun:test";
|
|
6
|
+
import { EventBus } from "../EventBus";
|
|
7
|
+
import type { BusEvent } from "../types";
|
|
8
|
+
|
|
9
|
+
// Test event factory
|
|
10
|
+
function createEvent(type: string, data: unknown = {}): BusEvent {
|
|
11
|
+
return {
|
|
12
|
+
type,
|
|
13
|
+
timestamp: Date.now(),
|
|
14
|
+
data,
|
|
15
|
+
source: "agent",
|
|
16
|
+
category: "message",
|
|
17
|
+
intent: "notification",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("EventBus", () => {
|
|
22
|
+
let bus: EventBus;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
bus = new EventBus();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("emit and on", () => {
|
|
29
|
+
it("should emit event to subscriber", () => {
|
|
30
|
+
const received: BusEvent[] = [];
|
|
31
|
+
bus.on("test_event", (e) => received.push(e));
|
|
32
|
+
|
|
33
|
+
bus.emit(createEvent("test_event", { value: 1 }));
|
|
34
|
+
|
|
35
|
+
expect(received).toHaveLength(1);
|
|
36
|
+
expect(received[0].type).toBe("test_event");
|
|
37
|
+
expect(received[0].data).toEqual({ value: 1 });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should not receive events of different type", () => {
|
|
41
|
+
const received: BusEvent[] = [];
|
|
42
|
+
bus.on("type_a", (e) => received.push(e));
|
|
43
|
+
|
|
44
|
+
bus.emit(createEvent("type_b"));
|
|
45
|
+
|
|
46
|
+
expect(received).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should support multiple subscribers", () => {
|
|
50
|
+
let count = 0;
|
|
51
|
+
bus.on("test", () => count++);
|
|
52
|
+
bus.on("test", () => count++);
|
|
53
|
+
|
|
54
|
+
bus.emit(createEvent("test"));
|
|
55
|
+
|
|
56
|
+
expect(count).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should support array of event types", () => {
|
|
60
|
+
const received: string[] = [];
|
|
61
|
+
bus.on(["type_a", "type_b"], (e) => received.push(e.type));
|
|
62
|
+
|
|
63
|
+
bus.emit(createEvent("type_a"));
|
|
64
|
+
bus.emit(createEvent("type_b"));
|
|
65
|
+
bus.emit(createEvent("type_c"));
|
|
66
|
+
|
|
67
|
+
expect(received).toEqual(["type_a", "type_b"]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("unsubscribe", () => {
|
|
72
|
+
it("should stop receiving events after unsubscribe", () => {
|
|
73
|
+
const received: BusEvent[] = [];
|
|
74
|
+
const unsubscribe = bus.on("test", (e) => received.push(e));
|
|
75
|
+
|
|
76
|
+
bus.emit(createEvent("test"));
|
|
77
|
+
expect(received).toHaveLength(1);
|
|
78
|
+
|
|
79
|
+
unsubscribe();
|
|
80
|
+
|
|
81
|
+
bus.emit(createEvent("test"));
|
|
82
|
+
expect(received).toHaveLength(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("onAny", () => {
|
|
87
|
+
it("should receive all events", () => {
|
|
88
|
+
const received: string[] = [];
|
|
89
|
+
bus.onAny((e) => received.push(e.type));
|
|
90
|
+
|
|
91
|
+
bus.emit(createEvent("type_a"));
|
|
92
|
+
bus.emit(createEvent("type_b"));
|
|
93
|
+
bus.emit(createEvent("type_c"));
|
|
94
|
+
|
|
95
|
+
expect(received).toEqual(["type_a", "type_b", "type_c"]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("once", () => {
|
|
100
|
+
it("should only receive first event", () => {
|
|
101
|
+
let count = 0;
|
|
102
|
+
bus.once("test", () => count++);
|
|
103
|
+
|
|
104
|
+
bus.emit(createEvent("test"));
|
|
105
|
+
bus.emit(createEvent("test"));
|
|
106
|
+
bus.emit(createEvent("test"));
|
|
107
|
+
|
|
108
|
+
expect(count).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("filter option", () => {
|
|
113
|
+
it("should filter events", () => {
|
|
114
|
+
const received: BusEvent[] = [];
|
|
115
|
+
bus.on("test", (e) => received.push(e), {
|
|
116
|
+
filter: (e) => (e.data as { value: number }).value > 5,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
bus.emit(createEvent("test", { value: 3 }));
|
|
120
|
+
bus.emit(createEvent("test", { value: 7 }));
|
|
121
|
+
bus.emit(createEvent("test", { value: 10 }));
|
|
122
|
+
|
|
123
|
+
expect(received).toHaveLength(2);
|
|
124
|
+
expect((received[0].data as { value: number }).value).toBe(7);
|
|
125
|
+
expect((received[1].data as { value: number }).value).toBe(10);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("priority option", () => {
|
|
130
|
+
it("should execute higher priority handlers first", () => {
|
|
131
|
+
const order: number[] = [];
|
|
132
|
+
|
|
133
|
+
bus.on("test", () => order.push(1), { priority: 1 });
|
|
134
|
+
bus.on("test", () => order.push(10), { priority: 10 });
|
|
135
|
+
bus.on("test", () => order.push(5), { priority: 5 });
|
|
136
|
+
|
|
137
|
+
bus.emit(createEvent("test"));
|
|
138
|
+
|
|
139
|
+
expect(order).toEqual([10, 5, 1]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("emitBatch", () => {
|
|
144
|
+
it("should emit multiple events", () => {
|
|
145
|
+
const received: string[] = [];
|
|
146
|
+
bus.onAny((e) => received.push(e.type));
|
|
147
|
+
|
|
148
|
+
bus.emitBatch([createEvent("a"), createEvent("b"), createEvent("c")]);
|
|
149
|
+
|
|
150
|
+
expect(received).toEqual(["a", "b", "c"]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("asProducer and asConsumer", () => {
|
|
155
|
+
it("should return producer view with only emit methods", () => {
|
|
156
|
+
const producer = bus.asProducer();
|
|
157
|
+
|
|
158
|
+
expect(typeof producer.emit).toBe("function");
|
|
159
|
+
expect(typeof producer.emitBatch).toBe("function");
|
|
160
|
+
expect(typeof producer.emitCommand).toBe("function");
|
|
161
|
+
expect((producer as unknown as { on?: unknown }).on).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should return consumer view with only subscribe methods", () => {
|
|
165
|
+
const consumer = bus.asConsumer();
|
|
166
|
+
|
|
167
|
+
expect(typeof consumer.on).toBe("function");
|
|
168
|
+
expect(typeof consumer.onAny).toBe("function");
|
|
169
|
+
expect(typeof consumer.once).toBe("function");
|
|
170
|
+
expect(typeof consumer.onCommand).toBe("function");
|
|
171
|
+
expect(typeof consumer.request).toBe("function");
|
|
172
|
+
expect((consumer as unknown as { emit?: unknown }).emit).toBeUndefined();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should cache views", () => {
|
|
176
|
+
const producer1 = bus.asProducer();
|
|
177
|
+
const producer2 = bus.asProducer();
|
|
178
|
+
expect(producer1).toBe(producer2);
|
|
179
|
+
|
|
180
|
+
const consumer1 = bus.asConsumer();
|
|
181
|
+
const consumer2 = bus.asConsumer();
|
|
182
|
+
expect(consumer1).toBe(consumer2);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("producer emit should work", () => {
|
|
186
|
+
const received: BusEvent[] = [];
|
|
187
|
+
bus.on("test", (e) => received.push(e));
|
|
188
|
+
|
|
189
|
+
const producer = bus.asProducer();
|
|
190
|
+
producer.emit(createEvent("test"));
|
|
191
|
+
|
|
192
|
+
expect(received).toHaveLength(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("consumer on should work", () => {
|
|
196
|
+
const received: BusEvent[] = [];
|
|
197
|
+
const consumer = bus.asConsumer();
|
|
198
|
+
consumer.on("test", (e) => received.push(e));
|
|
199
|
+
|
|
200
|
+
bus.emit(createEvent("test"));
|
|
201
|
+
|
|
202
|
+
expect(received).toHaveLength(1);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("destroy", () => {
|
|
207
|
+
it("should stop emitting after destroy", () => {
|
|
208
|
+
const received: BusEvent[] = [];
|
|
209
|
+
bus.on("test", (e) => received.push(e));
|
|
210
|
+
|
|
211
|
+
bus.destroy();
|
|
212
|
+
bus.emit(createEvent("test"));
|
|
213
|
+
|
|
214
|
+
expect(received).toHaveLength(0);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should return no-op unsubscribe after destroy", () => {
|
|
218
|
+
bus.destroy();
|
|
219
|
+
const unsubscribe = bus.on("test", () => {});
|
|
220
|
+
|
|
221
|
+
// Should not throw
|
|
222
|
+
expect(() => unsubscribe()).not.toThrow();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should be idempotent", () => {
|
|
226
|
+
expect(() => {
|
|
227
|
+
bus.destroy();
|
|
228
|
+
bus.destroy();
|
|
229
|
+
bus.destroy();
|
|
230
|
+
}).not.toThrow();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("error handling", () => {
|
|
235
|
+
it("should not stop other handlers on error", () => {
|
|
236
|
+
const received: number[] = [];
|
|
237
|
+
|
|
238
|
+
bus.on("test", () => received.push(1));
|
|
239
|
+
bus.on("test", () => {
|
|
240
|
+
throw new Error("Handler error");
|
|
241
|
+
});
|
|
242
|
+
bus.on("test", () => received.push(3));
|
|
243
|
+
|
|
244
|
+
// Should not throw
|
|
245
|
+
expect(() => bus.emit(createEvent("test"))).not.toThrow();
|
|
246
|
+
|
|
247
|
+
// Other handlers should still run
|
|
248
|
+
expect(received).toEqual([1, 3]);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Module
|
|
3
|
+
*
|
|
4
|
+
* Central event bus system for ecosystem communication.
|
|
5
|
+
* Provides type-safe pub/sub event handling with RxJS.
|
|
6
|
+
*
|
|
7
|
+
* ## Architecture
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* EventBus (interface)
|
|
11
|
+
* ├── EventProducer (write-only view)
|
|
12
|
+
* └── EventConsumer (read-only view)
|
|
13
|
+
*
|
|
14
|
+
* EventBusImpl (implementation)
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ## Usage
|
|
18
|
+
*
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { EventBusImpl, type EventBus } from "@agentxjs/core/event";
|
|
21
|
+
*
|
|
22
|
+
* const bus: EventBus = new EventBusImpl();
|
|
23
|
+
*
|
|
24
|
+
* // Subscribe to events
|
|
25
|
+
* bus.on("text_delta", (event) => {
|
|
26
|
+
* console.log(event.data.text);
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Emit events
|
|
30
|
+
* bus.emit({
|
|
31
|
+
* type: "text_delta",
|
|
32
|
+
* timestamp: Date.now(),
|
|
33
|
+
* data: { text: "Hello" },
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Get restricted views for components
|
|
37
|
+
* const producer = bus.asProducer(); // Can only emit
|
|
38
|
+
* const consumer = bus.asConsumer(); // Can only subscribe
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// Type definitions from types directory
|
|
43
|
+
export * from "./types";
|
|
44
|
+
|
|
45
|
+
// Implementation
|
|
46
|
+
export { EventBusImpl } from "./EventBus";
|