@memnexus-ai/mx-agent-cli 0.1.125 → 0.1.126

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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Unit tests for MessageBus.
3
+ *
4
+ * Uses a mock RedisClient implementation (no real Redis or ioredis import).
5
+ * Covers lifecycle, send, receive routing, inbox buffering, and presence.
6
+ *
7
+ * Part of Phase 1 of the agent message queue PRD.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=message-bus.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/message-bus.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,524 @@
1
+ /**
2
+ * Unit tests for MessageBus.
3
+ *
4
+ * Uses a mock RedisClient implementation (no real Redis or ioredis import).
5
+ * Covers lifecycle, send, receive routing, inbox buffering, and presence.
6
+ *
7
+ * Part of Phase 1 of the agent message queue PRD.
8
+ */
9
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import { parseStreamEntry, MessageBus, TEAM_NAME_RE } from '../lib/message-bus.js';
11
+ // -- Helpers ------------------------------------------------------------------
12
+ /** Small delay — simulates the BLOCK timeout in the mock xreadgroup.
13
+ * Without this, the readLoop spins in a tight loop and exhausts memory. */
14
+ function delay(ms) {
15
+ return new Promise((r) => setTimeout(r, ms));
16
+ }
17
+ /** Build a minimal AgentMessage for testing. */
18
+ function makeAgentMessage(overrides = {}) {
19
+ return {
20
+ id: 'test-id',
21
+ from: 'team-alpha',
22
+ to: 'team-beta',
23
+ type: 'notify',
24
+ priority: 'p2',
25
+ content: 'hello',
26
+ timestamp: '2026-03-30T00:00:00.000Z',
27
+ ...overrides,
28
+ };
29
+ }
30
+ /** Serialize an AgentMessage into the flat key/value array that Redis returns. */
31
+ function flattenMessage(msg) {
32
+ const pairs = [];
33
+ for (const [key, value] of Object.entries(msg)) {
34
+ if (value !== undefined) {
35
+ pairs.push(key, String(value));
36
+ }
37
+ }
38
+ return pairs;
39
+ }
40
+ // -- Mock RedisClient ---------------------------------------------------------
41
+ /** Create a mock RedisClient with all methods used by MessageBus.
42
+ * The default xreadgroup simulates a BLOCK timeout via a short delay. */
43
+ function createMockRedis() {
44
+ return {
45
+ connect: vi.fn().mockResolvedValue(undefined),
46
+ disconnect: vi.fn(),
47
+ xgroup: vi.fn().mockResolvedValue('OK'),
48
+ xadd: vi.fn().mockResolvedValue('1234567890-0'),
49
+ // Simulate BLOCK timeout — must have a delay to prevent tight-loop OOM
50
+ xreadgroup: vi.fn().mockImplementation(() => delay(100).then(() => null)),
51
+ xack: vi.fn().mockResolvedValue(1),
52
+ sadd: vi.fn().mockResolvedValue(1),
53
+ srem: vi.fn().mockResolvedValue(1),
54
+ smembers: vi.fn().mockResolvedValue([]),
55
+ set: vi.fn().mockResolvedValue('OK'),
56
+ del: vi.fn().mockResolvedValue(1),
57
+ };
58
+ }
59
+ // -- Per-test state -----------------------------------------------------------
60
+ let redis;
61
+ beforeEach(() => {
62
+ redis = createMockRedis();
63
+ });
64
+ afterEach(() => {
65
+ vi.restoreAllMocks();
66
+ });
67
+ // -- parseStreamEntry ---------------------------------------------------------
68
+ describe('parseStreamEntry', () => {
69
+ it('converts a flat key/value array into an AgentMessage', () => {
70
+ const fields = ['id', 'msg-1', 'from', 'alpha', 'to', 'beta', 'type', 'notify',
71
+ 'priority', 'p2', 'content', 'hello', 'timestamp', '2026-01-01T00:00:00Z'];
72
+ const msg = parseStreamEntry(fields);
73
+ expect(msg.id).toBe('msg-1');
74
+ expect(msg.from).toBe('alpha');
75
+ expect(msg.to).toBe('beta');
76
+ expect(msg.type).toBe('notify');
77
+ expect(msg.priority).toBe('p2');
78
+ expect(msg.content).toBe('hello');
79
+ expect(msg.timestamp).toBe('2026-01-01T00:00:00Z');
80
+ });
81
+ it('handles optional fields gracefully', () => {
82
+ const fields = ['id', 'msg-2', 'from', 'x', 'to', 'y', 'type', 'task',
83
+ 'priority', 'p0', 'content', 'urgent', 'timestamp', '2026-01-01T00:00:00Z',
84
+ 'taskId', 'task-123', 'replyTo', 'msg-0'];
85
+ const msg = parseStreamEntry(fields);
86
+ expect(msg.taskId).toBe('task-123');
87
+ expect(msg.replyTo).toBe('msg-0');
88
+ });
89
+ });
90
+ // -- MessageBus: start() ------------------------------------------------------
91
+ describe('MessageBus.start()', () => {
92
+ it('connects to Redis', async () => {
93
+ const bus = new MessageBus(redis, 'team-beta');
94
+ await bus.start();
95
+ expect(redis.connect).toHaveBeenCalledOnce();
96
+ await bus.stop();
97
+ });
98
+ it('creates consumer groups for team inbox and broadcast inbox', async () => {
99
+ const bus = new MessageBus(redis, 'team-beta');
100
+ await bus.start();
101
+ expect(redis.xgroup).toHaveBeenCalledWith('CREATE', 'mx:inbox:team-beta', 'team-beta-cg', '0', 'MKSTREAM');
102
+ expect(redis.xgroup).toHaveBeenCalledWith('CREATE', 'mx:inbox:all', 'team-beta-cg', '0', 'MKSTREAM');
103
+ await bus.stop();
104
+ });
105
+ it('ignores BUSYGROUP errors (group already exists)', async () => {
106
+ redis.xgroup.mockRejectedValueOnce(new Error('BUSYGROUP Consumer Group name already exists'));
107
+ const bus = new MessageBus(redis, 'team-beta');
108
+ // Should not throw
109
+ await bus.start();
110
+ await bus.stop();
111
+ });
112
+ it('propagates non-BUSYGROUP errors', async () => {
113
+ redis.xgroup.mockRejectedValueOnce(new Error('WRONGTYPE Operation'));
114
+ const bus = new MessageBus(redis, 'team-beta');
115
+ await expect(bus.start()).rejects.toThrow('WRONGTYPE');
116
+ // Clean up: stop will error since start didn't complete fully, but that's ok
117
+ bus.stop().catch(() => { });
118
+ });
119
+ it('registers in membership sets', async () => {
120
+ const bus = new MessageBus(redis, 'team-beta');
121
+ await bus.start();
122
+ expect(redis.sadd).toHaveBeenCalledWith('mx:known-streams', 'mx:inbox:team-beta');
123
+ expect(redis.sadd).toHaveBeenCalledWith('mx:online-agents', 'team-beta');
124
+ await bus.stop();
125
+ });
126
+ it('sets presence key with TTL on start', async () => {
127
+ const bus = new MessageBus(redis, 'team-beta');
128
+ await bus.start();
129
+ expect(redis.set).toHaveBeenCalledWith('mx:presence:team-beta', expect.stringContaining('"team":"team-beta"'), 'EX', 30);
130
+ await bus.stop();
131
+ });
132
+ });
133
+ // -- MessageBus: stop() -------------------------------------------------------
134
+ describe('MessageBus.stop()', () => {
135
+ it('removes from online agents set', async () => {
136
+ const bus = new MessageBus(redis, 'team-beta');
137
+ await bus.start();
138
+ redis.srem.mockClear();
139
+ await bus.stop();
140
+ expect(redis.srem).toHaveBeenCalledWith('mx:online-agents', 'team-beta');
141
+ });
142
+ it('deletes presence key', async () => {
143
+ const bus = new MessageBus(redis, 'team-beta');
144
+ await bus.start();
145
+ redis.del.mockClear();
146
+ await bus.stop();
147
+ expect(redis.del).toHaveBeenCalledWith('mx:presence:team-beta');
148
+ });
149
+ it('disconnects Redis', async () => {
150
+ const bus = new MessageBus(redis, 'team-beta');
151
+ await bus.start();
152
+ await bus.stop();
153
+ expect(redis.disconnect).toHaveBeenCalledOnce();
154
+ });
155
+ });
156
+ // -- MessageBus: notify() -----------------------------------------------------
157
+ describe('MessageBus.notify()', () => {
158
+ it('calls XADD with correct stream key for targeted message', async () => {
159
+ const bus = new MessageBus(redis, 'team-alpha');
160
+ await bus.start();
161
+ await bus.notify('team-beta', 'check this out', 'p2');
162
+ expect(redis.xadd).toHaveBeenCalledOnce();
163
+ const args = redis.xadd.mock.calls[0];
164
+ // First arg: stream key
165
+ expect(args[0]).toBe('mx:inbox:team-beta');
166
+ // MAXLEN args
167
+ expect(args[1]).toBe('MAXLEN');
168
+ expect(args[2]).toBe('~');
169
+ expect(args[3]).toBe('1000');
170
+ expect(args[4]).toBe('*');
171
+ // Message fields should contain the content
172
+ expect(args).toContain('content');
173
+ expect(args).toContain('check this out');
174
+ await bus.stop();
175
+ });
176
+ it('uses mx:inbox:all for broadcast messages', async () => {
177
+ const bus = new MessageBus(redis, 'team-alpha');
178
+ await bus.start();
179
+ await bus.notify('all', 'broadcast message');
180
+ const args = redis.xadd.mock.calls[0];
181
+ expect(args[0]).toBe('mx:inbox:all');
182
+ await bus.stop();
183
+ });
184
+ it('returns the message id', async () => {
185
+ const bus = new MessageBus(redis, 'team-alpha');
186
+ await bus.start();
187
+ const id = await bus.notify('team-beta', 'hello');
188
+ expect(typeof id).toBe('string');
189
+ expect(id.length).toBeGreaterThan(0);
190
+ await bus.stop();
191
+ });
192
+ it('defaults priority to p2', async () => {
193
+ const bus = new MessageBus(redis, 'team-alpha');
194
+ await bus.start();
195
+ await bus.notify('team-beta', 'hello');
196
+ const args = redis.xadd.mock.calls[0];
197
+ // The serialized message should contain priority p2
198
+ const priorityIndex = args.indexOf('priority');
199
+ expect(priorityIndex).toBeGreaterThan(-1);
200
+ expect(args[priorityIndex + 1]).toBe('p2');
201
+ await bus.stop();
202
+ });
203
+ it('serializes all message fields including from and type', async () => {
204
+ const bus = new MessageBus(redis, 'team-alpha');
205
+ await bus.start();
206
+ await bus.notify('team-beta', 'test', 'p1');
207
+ const args = redis.xadd.mock.calls[0];
208
+ // Should have 'from' set to the bus's team
209
+ const fromIndex = args.indexOf('from');
210
+ expect(fromIndex).toBeGreaterThan(-1);
211
+ expect(args[fromIndex + 1]).toBe('team-alpha');
212
+ // Should have 'type' set to 'notify'
213
+ const typeIndex = args.indexOf('type');
214
+ expect(typeIndex).toBeGreaterThan(-1);
215
+ expect(args[typeIndex + 1]).toBe('notify');
216
+ await bus.stop();
217
+ });
218
+ });
219
+ // -- MessageBus: handleMessage routing ----------------------------------------
220
+ describe('MessageBus message routing', () => {
221
+ it('routes P0 messages to onInject callback', async () => {
222
+ const bus = new MessageBus(redis, 'team-beta');
223
+ const injected = [];
224
+ bus.onInject = (msg) => injected.push(msg);
225
+ const p0msg = makeAgentMessage({ priority: 'p0', content: 'critical' });
226
+ let callCount = 0;
227
+ redis.xreadgroup.mockImplementation(async () => {
228
+ callCount++;
229
+ if (callCount === 1) {
230
+ return [['mx:inbox:team-beta', [['1-0', flattenMessage(p0msg)]]]];
231
+ }
232
+ await delay(100);
233
+ return null;
234
+ });
235
+ await bus.start();
236
+ await delay(50);
237
+ expect(injected).toHaveLength(1);
238
+ expect(injected[0].priority).toBe('p0');
239
+ expect(injected[0].content).toBe('critical');
240
+ await bus.stop();
241
+ });
242
+ it('routes P1 messages to onInject callback', async () => {
243
+ const bus = new MessageBus(redis, 'team-beta');
244
+ const injected = [];
245
+ bus.onInject = (msg) => injected.push(msg);
246
+ const p1msg = makeAgentMessage({ priority: 'p1', content: 'high-pri' });
247
+ let callCount = 0;
248
+ redis.xreadgroup.mockImplementation(async () => {
249
+ callCount++;
250
+ if (callCount === 1) {
251
+ return [['mx:inbox:team-beta', [['1-0', flattenMessage(p1msg)]]]];
252
+ }
253
+ await delay(100);
254
+ return null;
255
+ });
256
+ await bus.start();
257
+ await delay(50);
258
+ expect(injected).toHaveLength(1);
259
+ expect(injected[0].priority).toBe('p1');
260
+ await bus.stop();
261
+ });
262
+ it('buffers P2 messages in messageQueue (not injected)', async () => {
263
+ const bus = new MessageBus(redis, 'team-beta');
264
+ const injected = [];
265
+ bus.onInject = (msg) => injected.push(msg);
266
+ const p2msg = makeAgentMessage({ priority: 'p2', content: 'normal' });
267
+ let callCount = 0;
268
+ redis.xreadgroup.mockImplementation(async () => {
269
+ callCount++;
270
+ if (callCount === 1) {
271
+ return [['mx:inbox:team-beta', [['1-0', flattenMessage(p2msg)]]]];
272
+ }
273
+ await delay(100);
274
+ return null;
275
+ });
276
+ await bus.start();
277
+ await delay(50);
278
+ // P2 should NOT be injected
279
+ expect(injected).toHaveLength(0);
280
+ // But should be in the buffer
281
+ expect(bus.pendingCount).toBe(1);
282
+ await bus.stop();
283
+ });
284
+ it('buffers P3 messages in messageQueue', async () => {
285
+ const bus = new MessageBus(redis, 'team-beta');
286
+ const p3msg = makeAgentMessage({ priority: 'p3', content: 'low-pri' });
287
+ let callCount = 0;
288
+ redis.xreadgroup.mockImplementation(async () => {
289
+ callCount++;
290
+ if (callCount === 1) {
291
+ return [['mx:inbox:team-beta', [['1-0', flattenMessage(p3msg)]]]];
292
+ }
293
+ await delay(100);
294
+ return null;
295
+ });
296
+ await bus.start();
297
+ await delay(50);
298
+ expect(bus.pendingCount).toBe(1);
299
+ const drained = bus.drainInbox();
300
+ expect(drained[0].priority).toBe('p3');
301
+ await bus.stop();
302
+ });
303
+ it('ACKs messages after processing', async () => {
304
+ const bus = new MessageBus(redis, 'team-beta');
305
+ const msg = makeAgentMessage({ priority: 'p2' });
306
+ let callCount = 0;
307
+ redis.xreadgroup.mockImplementation(async () => {
308
+ callCount++;
309
+ if (callCount === 1) {
310
+ return [['mx:inbox:team-beta', [['1-0', flattenMessage(msg)]]]];
311
+ }
312
+ await delay(100);
313
+ return null;
314
+ });
315
+ await bus.start();
316
+ await delay(50);
317
+ expect(redis.xack).toHaveBeenCalledWith('mx:inbox:team-beta', 'team-beta-cg', '1-0');
318
+ await bus.stop();
319
+ });
320
+ });
321
+ // -- MessageBus: drainInbox ---------------------------------------------------
322
+ describe('MessageBus.drainInbox()', () => {
323
+ it('returns buffered messages and clears the buffer', async () => {
324
+ const bus = new MessageBus(redis, 'team-beta');
325
+ const msg1 = makeAgentMessage({ id: 'msg-1', priority: 'p2', content: 'first' });
326
+ const msg2 = makeAgentMessage({ id: 'msg-2', priority: 'p3', content: 'second' });
327
+ let callCount = 0;
328
+ redis.xreadgroup.mockImplementation(async () => {
329
+ callCount++;
330
+ if (callCount === 1) {
331
+ return [
332
+ ['mx:inbox:team-beta', [
333
+ ['1-0', flattenMessage(msg1)],
334
+ ['2-0', flattenMessage(msg2)],
335
+ ]],
336
+ ];
337
+ }
338
+ await delay(100);
339
+ return null;
340
+ });
341
+ await bus.start();
342
+ await delay(50);
343
+ expect(bus.pendingCount).toBe(2);
344
+ const drained = bus.drainInbox();
345
+ expect(drained).toHaveLength(2);
346
+ expect(drained[0].content).toBe('first');
347
+ expect(drained[1].content).toBe('second');
348
+ // Buffer should now be empty
349
+ expect(bus.pendingCount).toBe(0);
350
+ expect(bus.drainInbox()).toEqual([]);
351
+ await bus.stop();
352
+ });
353
+ });
354
+ // -- MessageBus: FIFO eviction ------------------------------------------------
355
+ describe('MessageBus queue eviction', () => {
356
+ it('evicts oldest message when maxQueueSize is reached', async () => {
357
+ const bus = new MessageBus(redis, 'team-beta', { maxQueueSize: 3 });
358
+ // Deliver 4 P2 messages in one batch -- the first should be evicted
359
+ const msgs = [
360
+ makeAgentMessage({ id: 'msg-1', priority: 'p2', content: 'one' }),
361
+ makeAgentMessage({ id: 'msg-2', priority: 'p2', content: 'two' }),
362
+ makeAgentMessage({ id: 'msg-3', priority: 'p2', content: 'three' }),
363
+ makeAgentMessage({ id: 'msg-4', priority: 'p2', content: 'four' }),
364
+ ];
365
+ let callCount = 0;
366
+ redis.xreadgroup.mockImplementation(async () => {
367
+ callCount++;
368
+ if (callCount === 1) {
369
+ return [
370
+ ['mx:inbox:team-beta', msgs.map((m, i) => [`${i + 1}-0`, flattenMessage(m)])],
371
+ ];
372
+ }
373
+ await delay(100);
374
+ return null;
375
+ });
376
+ await bus.start();
377
+ await delay(50);
378
+ // maxQueueSize is 3, so msg-1 should have been evicted
379
+ expect(bus.pendingCount).toBe(3);
380
+ const drained = bus.drainInbox();
381
+ expect(drained.map((m) => m.content)).toEqual(['two', 'three', 'four']);
382
+ await bus.stop();
383
+ });
384
+ });
385
+ // -- MessageBus: getOnlineAgents ----------------------------------------------
386
+ describe('MessageBus.getOnlineAgents()', () => {
387
+ it('returns members of the online agents set', async () => {
388
+ redis.smembers.mockResolvedValue(['team-alpha', 'team-beta', 'team-gamma']);
389
+ const bus = new MessageBus(redis, 'team-alpha');
390
+ await bus.start();
391
+ const agents = await bus.getOnlineAgents();
392
+ expect(agents).toEqual(['team-alpha', 'team-beta', 'team-gamma']);
393
+ expect(redis.smembers).toHaveBeenCalledWith('mx:online-agents');
394
+ await bus.stop();
395
+ });
396
+ });
397
+ // -- MessageBus: readLoop error handling --------------------------------------
398
+ describe('MessageBus readLoop error handling', () => {
399
+ it('continues reading after a transient error', async () => {
400
+ const bus = new MessageBus(redis, 'team-beta');
401
+ const msg = makeAgentMessage({ priority: 'p2', content: 'after-error' });
402
+ let callCount = 0;
403
+ redis.xreadgroup.mockImplementation(async () => {
404
+ callCount++;
405
+ if (callCount === 1) {
406
+ throw new Error('LOADING Redis is loading the dataset in memory');
407
+ }
408
+ if (callCount === 2) {
409
+ return [['mx:inbox:team-beta', [['1-0', flattenMessage(msg)]]]];
410
+ }
411
+ await delay(100);
412
+ return null;
413
+ });
414
+ // Suppress console.error output during test
415
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
416
+ await bus.start();
417
+ // Need enough time for error + 1s backoff + next read
418
+ await delay(1200);
419
+ expect(bus.pendingCount).toBe(1);
420
+ expect(bus.drainInbox()[0].content).toBe('after-error');
421
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('LOADING'));
422
+ errorSpy.mockRestore();
423
+ await bus.stop();
424
+ });
425
+ });
426
+ // -- Finding 1: Team name validation ------------------------------------------
427
+ describe('Team name validation', () => {
428
+ it('constructor throws on path-traversal team name', () => {
429
+ expect(() => new MessageBus(redis, '../../evil')).toThrow(/invalid local team name/);
430
+ });
431
+ it('constructor throws on empty team name', () => {
432
+ expect(() => new MessageBus(redis, '')).toThrow(/invalid local team name/);
433
+ });
434
+ it('constructor throws on team name with spaces', () => {
435
+ expect(() => new MessageBus(redis, 'has spaces')).toThrow(/invalid local team name/);
436
+ });
437
+ it('constructor accepts valid team names', () => {
438
+ expect(() => new MessageBus(redis, 'team-beta')).not.toThrow();
439
+ expect(() => new MessageBus(redis, 'ab')).not.toThrow();
440
+ expect(() => new MessageBus(redis, 'team-alpha-01')).not.toThrow();
441
+ });
442
+ it('notify() throws on invalid "to" parameter', async () => {
443
+ const bus = new MessageBus(redis, 'team-alpha');
444
+ await bus.start();
445
+ await expect(bus.notify('../../evil', 'hello')).rejects.toThrow(/invalid target team name/);
446
+ await expect(bus.notify('', 'hello')).rejects.toThrow(/invalid target team name/);
447
+ await bus.stop();
448
+ });
449
+ it('notify() allows "all" as target', async () => {
450
+ const bus = new MessageBus(redis, 'team-alpha');
451
+ await bus.start();
452
+ // Should not throw
453
+ await expect(bus.notify('all', 'broadcast')).resolves.toBeDefined();
454
+ await bus.stop();
455
+ });
456
+ it('TEAM_NAME_RE is exported for reuse', () => {
457
+ expect(TEAM_NAME_RE).toBeInstanceOf(RegExp);
458
+ expect(TEAM_NAME_RE.test('team-beta')).toBe(true);
459
+ expect(TEAM_NAME_RE.test('../../evil')).toBe(false);
460
+ });
461
+ });
462
+ // -- Finding 2: parseStreamEntry validation -----------------------------------
463
+ describe('parseStreamEntry validation', () => {
464
+ it('throws on missing required fields', () => {
465
+ // Missing 'content' and 'timestamp'
466
+ const fields = ['id', 'msg-1', 'from', 'alpha', 'to', 'beta', 'type', 'notify', 'priority', 'p2'];
467
+ expect(() => parseStreamEntry(fields)).toThrow(/missing required fields/);
468
+ });
469
+ it('throws on empty fields array', () => {
470
+ expect(() => parseStreamEntry([])).toThrow(/missing required fields/);
471
+ });
472
+ it('throws on invalid priority', () => {
473
+ const fields = [
474
+ 'id', 'msg-1', 'from', 'alpha', 'to', 'beta', 'type', 'notify',
475
+ 'priority', 'p9', 'content', 'hello', 'timestamp', '2026-01-01T00:00:00Z',
476
+ ];
477
+ expect(() => parseStreamEntry(fields)).toThrow(/invalid priority "p9"/);
478
+ });
479
+ it('throws on invalid message type', () => {
480
+ const fields = [
481
+ 'id', 'msg-1', 'from', 'alpha', 'to', 'beta', 'type', 'unknown',
482
+ 'priority', 'p2', 'content', 'hello', 'timestamp', '2026-01-01T00:00:00Z',
483
+ ];
484
+ expect(() => parseStreamEntry(fields)).toThrow(/invalid message type "unknown"/);
485
+ });
486
+ });
487
+ // -- Finding 2: readLoop continues after malformed message --------------------
488
+ describe('readLoop resilience to malformed messages', () => {
489
+ it('continues processing after a malformed message', async () => {
490
+ const bus = new MessageBus(redis, 'team-beta');
491
+ const goodMsg = makeAgentMessage({ id: 'good-1', priority: 'p2', content: 'valid' });
492
+ // Malformed: missing required fields
493
+ const badFields = ['id', 'bad-1', 'from', 'alpha'];
494
+ let callCount = 0;
495
+ redis.xreadgroup.mockImplementation(async () => {
496
+ callCount++;
497
+ if (callCount === 1) {
498
+ // First batch: one bad message, then one good message
499
+ return [
500
+ ['mx:inbox:team-beta', [
501
+ ['1-0', badFields],
502
+ ['2-0', flattenMessage(goodMsg)],
503
+ ]],
504
+ ];
505
+ }
506
+ await delay(100);
507
+ return null;
508
+ });
509
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
510
+ await bus.start();
511
+ await delay(50);
512
+ // The good message should still be buffered despite the bad one
513
+ expect(bus.pendingCount).toBe(1);
514
+ expect(bus.drainInbox()[0].content).toBe('valid');
515
+ // The bad message should have been logged
516
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('skipping malformed message 1-0'));
517
+ // Both messages should be ACKed (bad one too, to avoid re-delivery)
518
+ expect(redis.xack).toHaveBeenCalledWith('mx:inbox:team-beta', 'team-beta-cg', '1-0');
519
+ expect(redis.xack).toHaveBeenCalledWith('mx:inbox:team-beta', 'team-beta-cg', '2-0');
520
+ errorSpy.mockRestore();
521
+ await bus.stop();
522
+ });
523
+ });
524
+ //# sourceMappingURL=message-bus.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.test.js","sourceRoot":"","sources":["../../src/__tests__/message-bus.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * MessageBus -- Redis Streams transport for cross-team agent communication.
3
+ *
4
+ * Provides fire-and-forget notify() and background readLoop() for receiving
5
+ * messages from other agent teams. Messages are written to per-team Redis
6
+ * Streams and consumed via consumer groups.
7
+ *
8
+ * Part of Phase 1 of the agent message queue PRD.
9
+ * Phase 0 prerequisite: StreamInputController (stream-input-controller.ts)
10
+ */
11
+ export type Priority = 'p0' | 'p1' | 'p2' | 'p3';
12
+ export interface AgentMessage {
13
+ id: string;
14
+ from: string;
15
+ to: string;
16
+ type: 'notify' | 'task' | 'response' | 'task_update';
17
+ priority: Priority;
18
+ content: string;
19
+ task?: string;
20
+ taskId?: string;
21
+ payload?: string;
22
+ replyTo?: string;
23
+ timestamp: string;
24
+ expiresAt?: string;
25
+ }
26
+ /**
27
+ * Minimal Redis client interface used by MessageBus.
28
+ *
29
+ * Matches the ioredis API subset we use. In production, pass a real `Redis`
30
+ * instance from ioredis. In tests, pass a mock that implements this interface.
31
+ */
32
+ export interface RedisClient {
33
+ connect(): Promise<void>;
34
+ disconnect(): void;
35
+ xgroup(...args: unknown[]): Promise<unknown>;
36
+ xadd(...args: unknown[]): Promise<unknown>;
37
+ xreadgroup(...args: unknown[]): Promise<unknown[] | null>;
38
+ xack(...args: unknown[]): Promise<unknown>;
39
+ sadd(...args: unknown[]): Promise<unknown>;
40
+ srem(...args: unknown[]): Promise<unknown>;
41
+ smembers(key: string): Promise<string[]>;
42
+ set(...args: unknown[]): Promise<unknown>;
43
+ del(...args: unknown[]): Promise<unknown>;
44
+ }
45
+ /** Valid team names: lowercase alphanumeric + hyphens, 2-50 chars, no leading/trailing hyphens. */
46
+ export declare const TEAM_NAME_RE: RegExp;
47
+ /** Parse a flat Redis stream entry (alternating key/value array) into an AgentMessage. */
48
+ export declare function parseStreamEntry(fields: string[]): AgentMessage;
49
+ /**
50
+ * Create a RedisClient from a connection URL using ioredis.
51
+ *
52
+ * Callers must have ioredis installed. This is the standard factory for
53
+ * production use; tests should pass mock objects directly instead.
54
+ */
55
+ export declare function createRedisClient(redisUrl: string): Promise<RedisClient>;
56
+ export declare class MessageBus {
57
+ private redis;
58
+ private readonly team;
59
+ private readonly maxQueueSize;
60
+ private running;
61
+ private messageQueue;
62
+ private presenceInterval;
63
+ private readLoopPromise;
64
+ private readonly startedAt;
65
+ /** Callback invoked when a P0/P1 message or task response arrives.
66
+ * The harness wires this to StreamInputController.yield(). */
67
+ onInject?: (msg: AgentMessage) => void;
68
+ constructor(redis: RedisClient, team: string, options?: {
69
+ maxQueueSize?: number;
70
+ });
71
+ start(): Promise<void>;
72
+ stop(): Promise<void>;
73
+ /** Send a fire-and-forget notification to another team (or "all" for broadcast). */
74
+ notify(to: string, content: string, priority?: Priority): Promise<string>;
75
+ private readLoop;
76
+ private handleMessage;
77
+ private refreshPresence;
78
+ /** List currently online agent teams. */
79
+ getOnlineAgents(): Promise<string[]>;
80
+ /** Return buffered P2/P3 messages and clear the buffer. */
81
+ drainInbox(): AgentMessage[];
82
+ /** Number of buffered messages. */
83
+ get pendingCount(): number;
84
+ /** Serialize an AgentMessage into a flat key-value array for XADD. */
85
+ private serializeMessage;
86
+ }
87
+ //# sourceMappingURL=message-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.d.ts","sourceRoot":"","sources":["../../src/lib/message-bus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * MessageBus -- Redis Streams transport for cross-team agent communication.
3
+ *
4
+ * Provides fire-and-forget notify() and background readLoop() for receiving
5
+ * messages from other agent teams. Messages are written to per-team Redis
6
+ * Streams and consumed via consumer groups.
7
+ *
8
+ * Part of Phase 1 of the agent message queue PRD.
9
+ * Phase 0 prerequisite: StreamInputController (stream-input-controller.ts)
10
+ */
11
+ import { randomUUID as uuid } from 'crypto';
12
+ // -- Validation ---------------------------------------------------------------
13
+ /** Valid team names: lowercase alphanumeric + hyphens, 2-50 chars, no leading/trailing hyphens. */
14
+ export const TEAM_NAME_RE = /^[a-z][a-z0-9-]{0,48}[a-z0-9]$/;
15
+ function validateTeamName(name, context) {
16
+ if (name !== 'all' && !TEAM_NAME_RE.test(name)) {
17
+ throw new Error(`MessageBus: invalid ${context} team name "${name}". Must match ${TEAM_NAME_RE}`);
18
+ }
19
+ }
20
+ // -- Helpers ------------------------------------------------------------------
21
+ /** Parse a flat Redis stream entry (alternating key/value array) into an AgentMessage. */
22
+ export function parseStreamEntry(fields) {
23
+ const obj = {};
24
+ for (let i = 0; i < fields.length; i += 2) {
25
+ obj[fields[i]] = fields[i + 1];
26
+ }
27
+ // Validate required fields
28
+ const { id, from, to, type, priority, content, timestamp } = obj;
29
+ if (!id || !from || !to || !type || !priority || !content || !timestamp) {
30
+ throw new Error('MessageBus: malformed message — missing required fields');
31
+ }
32
+ if (!['p0', 'p1', 'p2', 'p3'].includes(priority)) {
33
+ throw new Error(`MessageBus: invalid priority "${priority}"`);
34
+ }
35
+ if (!['notify', 'task', 'response', 'task_update'].includes(type)) {
36
+ throw new Error(`MessageBus: invalid message type "${type}"`);
37
+ }
38
+ return obj;
39
+ }
40
+ /**
41
+ * Create a RedisClient from a connection URL using ioredis.
42
+ *
43
+ * Callers must have ioredis installed. This is the standard factory for
44
+ * production use; tests should pass mock objects directly instead.
45
+ */
46
+ export async function createRedisClient(redisUrl) {
47
+ const parsed = new URL(redisUrl);
48
+ if (parsed.protocol === 'redis:' && process.env.NODE_ENV === 'production') {
49
+ throw new Error('MessageBus: production Redis connections must use TLS (rediss:// URL)');
50
+ }
51
+ if (parsed.protocol === 'redis:') {
52
+ console.warn('[MessageBus] WARNING: Redis connection is not using TLS. Use rediss:// in production.');
53
+ }
54
+ const { Redis } = await import('ioredis');
55
+ return new Redis(redisUrl, { lazyConnect: true, maxRetriesPerRequest: 3 });
56
+ }
57
+ // -- MessageBus ---------------------------------------------------------------
58
+ export class MessageBus {
59
+ redis;
60
+ team;
61
+ maxQueueSize;
62
+ running = false;
63
+ messageQueue = [];
64
+ presenceInterval = null;
65
+ readLoopPromise = null;
66
+ startedAt;
67
+ /** Callback invoked when a P0/P1 message or task response arrives.
68
+ * The harness wires this to StreamInputController.yield(). */
69
+ onInject;
70
+ constructor(redis, team, options) {
71
+ validateTeamName(team, 'local');
72
+ this.redis = redis;
73
+ this.team = team;
74
+ this.maxQueueSize = options?.maxQueueSize ?? 100;
75
+ this.startedAt = new Date().toISOString();
76
+ }
77
+ // -- Lifecycle --------------------------------------------------------------
78
+ async start() {
79
+ await this.redis.connect();
80
+ this.running = true;
81
+ // Create consumer groups (ignore BUSYGROUP -- group already exists)
82
+ const streams = [`mx:inbox:${this.team}`, 'mx:inbox:all'];
83
+ for (const stream of streams) {
84
+ await this.redis
85
+ .xgroup('CREATE', stream, `${this.team}-cg`, '0', 'MKSTREAM')
86
+ .catch((err) => {
87
+ if (err instanceof Error && !err.message.includes('BUSYGROUP'))
88
+ throw err;
89
+ });
90
+ }
91
+ // Register in membership sets
92
+ await this.redis.sadd('mx:known-streams', `mx:inbox:${this.team}`);
93
+ await this.redis.sadd('mx:online-agents', this.team);
94
+ // Start presence heartbeat (30s TTL, refresh every 15s)
95
+ await this.refreshPresence();
96
+ this.presenceInterval = setInterval(() => void this.refreshPresence(), 15_000);
97
+ // Start background stream reader
98
+ this.readLoopPromise = this.readLoop();
99
+ }
100
+ async stop() {
101
+ this.running = false;
102
+ // Stop presence heartbeat
103
+ if (this.presenceInterval) {
104
+ clearInterval(this.presenceInterval);
105
+ this.presenceInterval = null;
106
+ }
107
+ // Remove from online set and delete presence key
108
+ await this.redis.srem('mx:online-agents', this.team).catch(() => { });
109
+ await this.redis.del(`mx:presence:${this.team}`).catch(() => { });
110
+ // Wait for readLoop to finish (it checks this.running)
111
+ if (this.readLoopPromise) {
112
+ await this.readLoopPromise.catch(() => { });
113
+ }
114
+ // Disconnect Redis
115
+ this.redis.disconnect();
116
+ }
117
+ // -- Send -------------------------------------------------------------------
118
+ /** Send a fire-and-forget notification to another team (or "all" for broadcast). */
119
+ async notify(to, content, priority = 'p2') {
120
+ validateTeamName(to, 'target');
121
+ const msg = {
122
+ id: uuid(),
123
+ from: this.team,
124
+ to,
125
+ type: 'notify',
126
+ priority,
127
+ content,
128
+ timestamp: new Date().toISOString(),
129
+ };
130
+ const stream = to === 'all' ? 'mx:inbox:all' : `mx:inbox:${to}`;
131
+ await this.redis.xadd(stream, 'MAXLEN', '~', '1000', '*', ...this.serializeMessage(msg));
132
+ return msg.id;
133
+ }
134
+ // -- Receive ----------------------------------------------------------------
135
+ async readLoop() {
136
+ while (this.running) {
137
+ try {
138
+ // XREADGROUP with COUNT + BLOCK -- returns null on timeout.
139
+ // ioredis overload order: GROUP, group, consumer, COUNT, count, BLOCK, ms, STREAMS, ...keys, ...ids
140
+ const results = await this.redis.xreadgroup('GROUP', `${this.team}-cg`, `${this.team}-reader`, 'COUNT', 10, 'BLOCK', 5000, 'STREAMS', `mx:inbox:${this.team}`, 'mx:inbox:all', '>', '>');
141
+ if (!results)
142
+ continue;
143
+ for (const [stream, entries] of results) {
144
+ for (const [streamId, fields] of entries) {
145
+ try {
146
+ const msg = parseStreamEntry(fields);
147
+ this.handleMessage(msg);
148
+ }
149
+ catch (parseErr) {
150
+ console.error(`[MessageBus] skipping malformed message ${streamId}: ${parseErr instanceof Error ? parseErr.message : parseErr}`);
151
+ }
152
+ await this.redis.xack(stream, `${this.team}-cg`, streamId);
153
+ }
154
+ }
155
+ }
156
+ catch (err) {
157
+ if (this.running) {
158
+ console.error(`[MessageBus] readLoop error: ${err instanceof Error ? err.message : err}`);
159
+ await new Promise((r) => setTimeout(r, 1000)); // backoff
160
+ }
161
+ }
162
+ }
163
+ }
164
+ handleMessage(msg) {
165
+ if (msg.priority === 'p0' || msg.priority === 'p1') {
166
+ // Inject into agent conversation immediately
167
+ this.onInject?.(msg);
168
+ }
169
+ else {
170
+ // Buffer for later -- FIFO eviction at cap
171
+ if (this.messageQueue.length >= this.maxQueueSize) {
172
+ this.messageQueue.shift();
173
+ }
174
+ this.messageQueue.push(msg);
175
+ }
176
+ }
177
+ // -- Presence ---------------------------------------------------------------
178
+ async refreshPresence() {
179
+ await this.redis
180
+ .set(`mx:presence:${this.team}`, JSON.stringify({ team: this.team, pid: process.pid, startedAt: this.startedAt }), 'EX', 30)
181
+ .catch(() => { });
182
+ }
183
+ /** List currently online agent teams. */
184
+ async getOnlineAgents() {
185
+ return this.redis.smembers('mx:online-agents');
186
+ }
187
+ // -- Inbox ------------------------------------------------------------------
188
+ /** Return buffered P2/P3 messages and clear the buffer. */
189
+ drainInbox() {
190
+ const messages = [...this.messageQueue];
191
+ this.messageQueue = [];
192
+ return messages;
193
+ }
194
+ /** Number of buffered messages. */
195
+ get pendingCount() {
196
+ return this.messageQueue.length;
197
+ }
198
+ // -- Internal ---------------------------------------------------------------
199
+ /** Serialize an AgentMessage into a flat key-value array for XADD. */
200
+ serializeMessage(msg) {
201
+ const pairs = [];
202
+ for (const [key, value] of Object.entries(msg)) {
203
+ if (value !== undefined) {
204
+ pairs.push(key, String(value));
205
+ }
206
+ }
207
+ return pairs;
208
+ }
209
+ }
210
+ //# sourceMappingURL=message-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.js","sourceRoot":"","sources":["../../src/lib/message-bus.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memnexus-ai/mx-agent-cli",
3
- "version": "0.1.125",
3
+ "version": "0.1.126",
4
4
  "description": "CLI for creating and managing AI agent teams",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,8 @@
39
39
  "@anthropic-ai/claude-agent-sdk": "^0.2.63",
40
40
  "chalk": "^5.3.0",
41
41
  "commander": "^12.0.0",
42
- "execa": "^9.0.0"
42
+ "execa": "^9.0.0",
43
+ "ioredis": "^5.10.1"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/node": "^20.11.0",