@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.
Files changed (77) hide show
  1. package/package.json +31 -0
  2. package/src/agent/AgentStateMachine.ts +151 -0
  3. package/src/agent/README.md +296 -0
  4. package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
  5. package/src/agent/__tests__/createAgent.test.ts +728 -0
  6. package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
  7. package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
  8. package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
  9. package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
  10. package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
  11. package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
  12. package/src/agent/createAgent.ts +467 -0
  13. package/src/agent/engine/AgentProcessor.ts +106 -0
  14. package/src/agent/engine/MealyMachine.ts +184 -0
  15. package/src/agent/engine/internal/index.ts +35 -0
  16. package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
  17. package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
  18. package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
  19. package/src/agent/engine/mealy/Mealy.ts +308 -0
  20. package/src/agent/engine/mealy/Processor.ts +70 -0
  21. package/src/agent/engine/mealy/Sink.ts +56 -0
  22. package/src/agent/engine/mealy/Source.ts +51 -0
  23. package/src/agent/engine/mealy/Store.ts +98 -0
  24. package/src/agent/engine/mealy/combinators.ts +176 -0
  25. package/src/agent/engine/mealy/index.ts +45 -0
  26. package/src/agent/index.ts +106 -0
  27. package/src/agent/types/engine.ts +395 -0
  28. package/src/agent/types/event.ts +478 -0
  29. package/src/agent/types/index.ts +197 -0
  30. package/src/agent/types/message.ts +387 -0
  31. package/src/common/index.ts +8 -0
  32. package/src/common/logger/ConsoleLogger.ts +137 -0
  33. package/src/common/logger/LoggerFactoryImpl.ts +123 -0
  34. package/src/common/logger/index.ts +26 -0
  35. package/src/common/logger/types.ts +98 -0
  36. package/src/container/Container.ts +185 -0
  37. package/src/container/index.ts +44 -0
  38. package/src/container/types.ts +71 -0
  39. package/src/driver/index.ts +42 -0
  40. package/src/driver/types.ts +363 -0
  41. package/src/event/EventBus.ts +260 -0
  42. package/src/event/README.md +237 -0
  43. package/src/event/__tests__/EventBus.test.ts +251 -0
  44. package/src/event/index.ts +46 -0
  45. package/src/event/types/agent.ts +512 -0
  46. package/src/event/types/base.ts +241 -0
  47. package/src/event/types/bus.ts +429 -0
  48. package/src/event/types/command.ts +749 -0
  49. package/src/event/types/container.ts +471 -0
  50. package/src/event/types/driver.ts +452 -0
  51. package/src/event/types/index.ts +26 -0
  52. package/src/event/types/session.ts +314 -0
  53. package/src/image/Image.ts +203 -0
  54. package/src/image/index.ts +36 -0
  55. package/src/image/types.ts +77 -0
  56. package/src/index.ts +20 -0
  57. package/src/mq/OffsetGenerator.ts +48 -0
  58. package/src/mq/README.md +166 -0
  59. package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
  60. package/src/mq/index.ts +18 -0
  61. package/src/mq/types.ts +172 -0
  62. package/src/network/RpcClient.ts +455 -0
  63. package/src/network/index.ts +76 -0
  64. package/src/network/jsonrpc.ts +336 -0
  65. package/src/network/protocol.ts +90 -0
  66. package/src/network/types.ts +284 -0
  67. package/src/persistence/index.ts +27 -0
  68. package/src/persistence/types.ts +226 -0
  69. package/src/runtime/AgentXRuntime.ts +501 -0
  70. package/src/runtime/index.ts +56 -0
  71. package/src/runtime/types.ts +236 -0
  72. package/src/session/Session.ts +71 -0
  73. package/src/session/index.ts +25 -0
  74. package/src/session/types.ts +77 -0
  75. package/src/workspace/index.ts +27 -0
  76. package/src/workspace/types.ts +131 -0
  77. package/tsconfig.json +10 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Mealy.test.ts - Unit tests for Mealy Machine runtime
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, mock } from "bun:test";
6
+ import { Mealy, createMealy, type MealyConfig } from "../../../engine/mealy/Mealy";
7
+ import { MemoryStore } from "../../../engine/mealy/Store";
8
+ import type { Processor } from "../../../engine/mealy/Processor";
9
+ import type { Sink, SinkDefinition } from "../../../engine/mealy/Sink";
10
+
11
+ // Test types
12
+ interface TestState {
13
+ count: number;
14
+ buffer: string[];
15
+ }
16
+
17
+ interface TestEvent {
18
+ type: string;
19
+ value?: number;
20
+ text?: string;
21
+ }
22
+
23
+ // Test processor
24
+ const testProcessor: Processor<TestState, TestEvent, TestEvent> = (state, event) => {
25
+ switch (event.type) {
26
+ case "increment":
27
+ return [
28
+ { ...state, count: state.count + (event.value || 1) },
29
+ [{ type: "incremented", value: state.count + (event.value || 1) }],
30
+ ];
31
+ case "append":
32
+ return [
33
+ { ...state, buffer: [...state.buffer, event.text || ""] },
34
+ [{ type: "appended", text: event.text }],
35
+ ];
36
+ case "reset":
37
+ return [{ count: 0, buffer: [] }, [{ type: "reset_done" }]];
38
+ case "multi_output":
39
+ return [state, [{ type: "output_1" }, { type: "output_2" }, { type: "output_3" }]];
40
+ default:
41
+ return [state, []];
42
+ }
43
+ };
44
+
45
+ const initialState: TestState = { count: 0, buffer: [] };
46
+
47
+ describe("Mealy", () => {
48
+ let store: MemoryStore<TestState>;
49
+ let config: MealyConfig<TestState, TestEvent>;
50
+ let mealy: Mealy<TestState, TestEvent>;
51
+
52
+ beforeEach(() => {
53
+ store = new MemoryStore<TestState>();
54
+ config = {
55
+ processor: testProcessor,
56
+ store,
57
+ initialState: { ...initialState },
58
+ recursive: false, // Disable recursion for basic tests
59
+ };
60
+ mealy = new Mealy(config);
61
+ });
62
+
63
+ describe("process", () => {
64
+ it("should process event and return result", () => {
65
+ const result = mealy.process("agent1", { type: "increment", value: 5 });
66
+
67
+ expect(result.state.count).toBe(5);
68
+ expect(result.outputs).toHaveLength(1);
69
+ expect(result.outputs[0]).toEqual({ type: "incremented", value: 5 });
70
+ expect(result.processCount).toBe(1);
71
+ });
72
+
73
+ it("should persist state between calls", () => {
74
+ mealy.process("agent1", { type: "increment", value: 5 });
75
+ const result = mealy.process("agent1", { type: "increment", value: 3 });
76
+
77
+ expect(result.state.count).toBe(8);
78
+ });
79
+
80
+ it("should isolate state by agent ID", () => {
81
+ mealy.process("agent1", { type: "increment", value: 10 });
82
+ mealy.process("agent2", { type: "increment", value: 20 });
83
+
84
+ expect(mealy.getState("agent1")?.count).toBe(10);
85
+ expect(mealy.getState("agent2")?.count).toBe(20);
86
+ });
87
+
88
+ it("should use initial state for new agents", () => {
89
+ const result = mealy.process("new_agent", { type: "increment", value: 1 });
90
+
91
+ expect(result.state.count).toBe(1); // Started from 0
92
+ });
93
+
94
+ it("should handle events with no output", () => {
95
+ const result = mealy.process("agent1", { type: "unknown" });
96
+
97
+ expect(result.outputs).toHaveLength(0);
98
+ expect(result.processCount).toBe(1);
99
+ });
100
+
101
+ it("should handle events with multiple outputs", () => {
102
+ const result = mealy.process("agent1", { type: "multi_output" });
103
+
104
+ expect(result.outputs).toHaveLength(3);
105
+ expect(result.outputs[0]).toEqual({ type: "output_1" });
106
+ expect(result.outputs[1]).toEqual({ type: "output_2" });
107
+ expect(result.outputs[2]).toEqual({ type: "output_3" });
108
+ });
109
+ });
110
+
111
+ describe("recursive processing", () => {
112
+ it("should recursively process outputs when recursive=true", () => {
113
+ // Processor that generates chain: A -> B -> C
114
+ const chainProcessor: Processor<{ depth: number }, TestEvent, TestEvent> = (state, event) => {
115
+ if (event.type === "start") {
116
+ return [{ depth: 1 }, [{ type: "level_1" }]];
117
+ }
118
+ if (event.type === "level_1") {
119
+ return [{ depth: 2 }, [{ type: "level_2" }]];
120
+ }
121
+ if (event.type === "level_2") {
122
+ return [{ depth: 3 }, [{ type: "level_3" }]];
123
+ }
124
+ return [state, []];
125
+ };
126
+
127
+ const recursiveMealy = createMealy({
128
+ processor: chainProcessor,
129
+ store: new MemoryStore(),
130
+ initialState: { depth: 0 },
131
+ recursive: true,
132
+ });
133
+
134
+ const result = recursiveMealy.process("agent", { type: "start" });
135
+
136
+ // Should have processed all 3 levels - outputs are accumulated
137
+ expect(result.outputs).toContainEqual({ type: "level_1" });
138
+ expect(result.outputs).toContainEqual({ type: "level_2" });
139
+ expect(result.outputs).toContainEqual({ type: "level_3" });
140
+
141
+ // Note: result.state is from the initial process call (depth=1)
142
+ // but the store has been updated by recursive calls
143
+ expect(result.state.depth).toBe(1); // First call's state
144
+ expect(recursiveMealy.getState("agent")?.depth).toBe(3); // Final state in store
145
+ });
146
+
147
+ it("should respect maxDepth limit", () => {
148
+ // Processor that generates infinite chain
149
+ const infiniteProcessor: Processor<{ count: number }, TestEvent, TestEvent> = (
150
+ state,
151
+ event
152
+ ) => {
153
+ if (event.type.startsWith("level_")) {
154
+ const next = state.count + 1;
155
+ return [{ count: next }, [{ type: `level_${next}` }]];
156
+ }
157
+ return [state, []];
158
+ };
159
+
160
+ const limitedMealy = createMealy({
161
+ processor: infiniteProcessor,
162
+ store: new MemoryStore(),
163
+ initialState: { count: 0 },
164
+ recursive: true,
165
+ maxDepth: 5,
166
+ });
167
+
168
+ const result = limitedMealy.process("agent", { type: "level_0" });
169
+
170
+ // Should stop at maxDepth
171
+ expect(result.state.count).toBeLessThanOrEqual(5);
172
+ });
173
+ });
174
+
175
+ describe("sinks", () => {
176
+ it("should send outputs to sink function", () => {
177
+ const sinkOutputs: TestEvent[][] = [];
178
+ const testSink: Sink<TestEvent> = (_id, outputs) => {
179
+ sinkOutputs.push(outputs);
180
+ };
181
+
182
+ const mealyWithSink = createMealy({
183
+ ...config,
184
+ sinks: [testSink],
185
+ });
186
+
187
+ mealyWithSink.process("agent1", { type: "increment", value: 5 });
188
+
189
+ expect(sinkOutputs).toHaveLength(1);
190
+ expect(sinkOutputs[0]).toContainEqual({ type: "incremented", value: 5 });
191
+ });
192
+
193
+ it("should send outputs to named SinkDefinition", () => {
194
+ const sinkOutputs: TestEvent[][] = [];
195
+ const namedSink: SinkDefinition<TestEvent> = {
196
+ name: "test-sink",
197
+ sink: (_id, outputs) => {
198
+ sinkOutputs.push(outputs);
199
+ },
200
+ };
201
+
202
+ const mealyWithSink = createMealy({
203
+ ...config,
204
+ sinks: [namedSink],
205
+ });
206
+
207
+ mealyWithSink.process("agent1", { type: "increment", value: 5 });
208
+
209
+ expect(sinkOutputs).toHaveLength(1);
210
+ });
211
+
212
+ it("should filter outputs with SinkDefinition.filter", () => {
213
+ const sinkOutputs: TestEvent[][] = [];
214
+ const filteredSink: SinkDefinition<TestEvent> = {
215
+ name: "filtered-sink",
216
+ filter: (event) => event.type === "appended",
217
+ sink: (_id, outputs) => {
218
+ sinkOutputs.push(outputs);
219
+ },
220
+ };
221
+
222
+ const mealyWithSink = createMealy({
223
+ ...config,
224
+ sinks: [filteredSink],
225
+ });
226
+
227
+ // This should be filtered out
228
+ mealyWithSink.process("agent1", { type: "increment", value: 5 });
229
+ expect(sinkOutputs).toHaveLength(0);
230
+
231
+ // This should pass through
232
+ mealyWithSink.process("agent1", { type: "append", text: "hello" });
233
+ expect(sinkOutputs).toHaveLength(1);
234
+ expect(sinkOutputs[0]).toContainEqual({ type: "appended", text: "hello" });
235
+ });
236
+
237
+ it("should handle async sinks", async () => {
238
+ const sinkOutputs: TestEvent[][] = [];
239
+ const asyncSink: Sink<TestEvent> = async (_id, outputs) => {
240
+ await new Promise((resolve) => setTimeout(resolve, 10));
241
+ sinkOutputs.push(outputs);
242
+ };
243
+
244
+ const mealyWithSink = createMealy({
245
+ ...config,
246
+ sinks: [asyncSink],
247
+ });
248
+
249
+ mealyWithSink.process("agent1", { type: "increment", value: 5 });
250
+
251
+ // Give async sink time to complete
252
+ await new Promise((resolve) => setTimeout(resolve, 50));
253
+
254
+ expect(sinkOutputs).toHaveLength(1);
255
+ });
256
+
257
+ it("should catch and log sink errors", () => {
258
+ const errorSink: Sink<TestEvent> = () => {
259
+ throw new Error("Sink error");
260
+ };
261
+
262
+ const mealyWithSink = createMealy({
263
+ ...config,
264
+ sinks: [errorSink],
265
+ });
266
+
267
+ // Should not throw
268
+ expect(() => {
269
+ mealyWithSink.process("agent1", { type: "increment", value: 5 });
270
+ }).not.toThrow();
271
+ });
272
+ });
273
+
274
+ describe("state management", () => {
275
+ it("should return state with getState", () => {
276
+ mealy.process("agent1", { type: "increment", value: 5 });
277
+
278
+ const state = mealy.getState("agent1");
279
+
280
+ expect(state).toEqual({ count: 5, buffer: [] });
281
+ });
282
+
283
+ it("should return undefined for non-existent agent", () => {
284
+ expect(mealy.getState("unknown")).toBeUndefined();
285
+ });
286
+
287
+ it("should check existence with hasState", () => {
288
+ expect(mealy.hasState("agent1")).toBe(false);
289
+
290
+ mealy.process("agent1", { type: "increment" });
291
+
292
+ expect(mealy.hasState("agent1")).toBe(true);
293
+ });
294
+
295
+ it("should remove state with cleanup", () => {
296
+ mealy.process("agent1", { type: "increment" });
297
+ expect(mealy.hasState("agent1")).toBe(true);
298
+
299
+ mealy.cleanup("agent1");
300
+
301
+ expect(mealy.hasState("agent1")).toBe(false);
302
+ });
303
+ });
304
+
305
+ describe("dynamic sink management", () => {
306
+ it("should add sink at runtime", () => {
307
+ const outputs: TestEvent[][] = [];
308
+ const dynamicSink: Sink<TestEvent> = (_id, o) => outputs.push(o);
309
+
310
+ mealy.process("agent1", { type: "increment" }); // No output yet
311
+ expect(outputs).toHaveLength(0);
312
+
313
+ mealy.addSink(dynamicSink);
314
+ mealy.process("agent1", { type: "increment" });
315
+
316
+ expect(outputs).toHaveLength(1);
317
+ });
318
+
319
+ it("should remove named sink", () => {
320
+ const outputs: TestEvent[][] = [];
321
+ const namedSink: SinkDefinition<TestEvent> = {
322
+ name: "removable",
323
+ sink: (_id, o) => outputs.push(o),
324
+ };
325
+
326
+ mealy.addSink(namedSink);
327
+ mealy.process("agent1", { type: "increment" });
328
+ expect(outputs).toHaveLength(1);
329
+
330
+ const removed = mealy.removeSink("removable");
331
+ expect(removed).toBe(true);
332
+
333
+ mealy.process("agent1", { type: "increment" });
334
+ expect(outputs).toHaveLength(1); // Still 1, no new output
335
+ });
336
+
337
+ it("should return false when removing non-existent sink", () => {
338
+ const removed = mealy.removeSink("non-existent");
339
+ expect(removed).toBe(false);
340
+ });
341
+ });
342
+ });
343
+
344
+ describe("createMealy", () => {
345
+ it("should create Mealy instance with factory function", () => {
346
+ const mealy = createMealy({
347
+ processor: testProcessor,
348
+ store: new MemoryStore(),
349
+ initialState,
350
+ });
351
+
352
+ expect(mealy).toBeInstanceOf(Mealy);
353
+
354
+ const result = mealy.process("test", { type: "increment", value: 1 });
355
+ expect(result.state.count).toBe(1);
356
+ });
357
+
358
+ it("should use default values for optional config", () => {
359
+ const mealy = createMealy({
360
+ processor: testProcessor,
361
+ store: new MemoryStore(),
362
+ initialState,
363
+ });
364
+
365
+ // Default recursive = true, maxDepth = 100
366
+ // Just verify it works
367
+ const result = mealy.process("test", { type: "increment" });
368
+ expect(result.processCount).toBeGreaterThanOrEqual(1);
369
+ });
370
+ });
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Store.test.ts - Unit tests for MemoryStore
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from "bun:test";
6
+ import { MemoryStore, type Store } from "../../../engine/mealy/Store";
7
+
8
+ describe("MemoryStore", () => {
9
+ let store: MemoryStore<{ count: number }>;
10
+
11
+ beforeEach(() => {
12
+ store = new MemoryStore<{ count: number }>();
13
+ });
14
+
15
+ describe("get/set", () => {
16
+ it("should return undefined for non-existent key", () => {
17
+ expect(store.get("unknown")).toBeUndefined();
18
+ });
19
+
20
+ it("should store and retrieve value", () => {
21
+ store.set("id1", { count: 10 });
22
+ expect(store.get("id1")).toEqual({ count: 10 });
23
+ });
24
+
25
+ it("should overwrite existing value", () => {
26
+ store.set("id1", { count: 10 });
27
+ store.set("id1", { count: 20 });
28
+ expect(store.get("id1")).toEqual({ count: 20 });
29
+ });
30
+
31
+ it("should store multiple keys independently", () => {
32
+ store.set("id1", { count: 1 });
33
+ store.set("id2", { count: 2 });
34
+ store.set("id3", { count: 3 });
35
+
36
+ expect(store.get("id1")).toEqual({ count: 1 });
37
+ expect(store.get("id2")).toEqual({ count: 2 });
38
+ expect(store.get("id3")).toEqual({ count: 3 });
39
+ });
40
+ });
41
+
42
+ describe("has", () => {
43
+ it("should return false for non-existent key", () => {
44
+ expect(store.has("unknown")).toBe(false);
45
+ });
46
+
47
+ it("should return true for existing key", () => {
48
+ store.set("id1", { count: 10 });
49
+ expect(store.has("id1")).toBe(true);
50
+ });
51
+ });
52
+
53
+ describe("delete", () => {
54
+ it("should remove existing key", () => {
55
+ store.set("id1", { count: 10 });
56
+ store.delete("id1");
57
+ expect(store.has("id1")).toBe(false);
58
+ expect(store.get("id1")).toBeUndefined();
59
+ });
60
+
61
+ it("should not throw when deleting non-existent key", () => {
62
+ expect(() => store.delete("unknown")).not.toThrow();
63
+ });
64
+ });
65
+
66
+ describe("clear", () => {
67
+ it("should remove all entries", () => {
68
+ store.set("id1", { count: 1 });
69
+ store.set("id2", { count: 2 });
70
+ store.clear();
71
+
72
+ expect(store.has("id1")).toBe(false);
73
+ expect(store.has("id2")).toBe(false);
74
+ expect(store.size).toBe(0);
75
+ });
76
+ });
77
+
78
+ describe("size", () => {
79
+ it("should return 0 for empty store", () => {
80
+ expect(store.size).toBe(0);
81
+ });
82
+
83
+ it("should return correct count", () => {
84
+ store.set("id1", { count: 1 });
85
+ expect(store.size).toBe(1);
86
+
87
+ store.set("id2", { count: 2 });
88
+ expect(store.size).toBe(2);
89
+
90
+ store.delete("id1");
91
+ expect(store.size).toBe(1);
92
+ });
93
+ });
94
+
95
+ describe("keys", () => {
96
+ it("should return empty iterator for empty store", () => {
97
+ const keys = Array.from(store.keys());
98
+ expect(keys).toEqual([]);
99
+ });
100
+
101
+ it("should return all stored keys", () => {
102
+ store.set("id1", { count: 1 });
103
+ store.set("id2", { count: 2 });
104
+
105
+ const keys = Array.from(store.keys());
106
+ expect(keys).toContain("id1");
107
+ expect(keys).toContain("id2");
108
+ expect(keys.length).toBe(2);
109
+ });
110
+ });
111
+
112
+ describe("Store interface compliance", () => {
113
+ it("should implement Store interface", () => {
114
+ const genericStore: Store<{ count: number }> = new MemoryStore();
115
+
116
+ genericStore.set("id", { count: 5 });
117
+ expect(genericStore.get("id")).toEqual({ count: 5 });
118
+ expect(genericStore.has("id")).toBe(true);
119
+ genericStore.delete("id");
120
+ expect(genericStore.has("id")).toBe(false);
121
+ });
122
+ });
123
+ });