@brutalist/mcp 0.6.0 → 0.6.1

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 (67) hide show
  1. package/README.md +3 -1
  2. package/dist/brutalist-server.d.ts +5 -0
  3. package/dist/brutalist-server.d.ts.map +1 -1
  4. package/dist/brutalist-server.js +244 -80
  5. package/dist/brutalist-server.js.map +1 -1
  6. package/dist/cli-agents.d.ts +7 -3
  7. package/dist/cli-agents.d.ts.map +1 -1
  8. package/dist/cli-agents.js +307 -48
  9. package/dist/cli-agents.js.map +1 -1
  10. package/dist/streaming/circuit-breaker.d.ts +186 -0
  11. package/dist/streaming/circuit-breaker.d.ts.map +1 -0
  12. package/dist/streaming/circuit-breaker.js +463 -0
  13. package/dist/streaming/circuit-breaker.js.map +1 -0
  14. package/dist/streaming/intelligent-buffer.d.ts +141 -0
  15. package/dist/streaming/intelligent-buffer.d.ts.map +1 -0
  16. package/dist/streaming/intelligent-buffer.js +555 -0
  17. package/dist/streaming/intelligent-buffer.js.map +1 -0
  18. package/dist/streaming/output-parser.d.ts +89 -0
  19. package/dist/streaming/output-parser.d.ts.map +1 -0
  20. package/dist/streaming/output-parser.js +349 -0
  21. package/dist/streaming/output-parser.js.map +1 -0
  22. package/dist/streaming/progress-tracker.d.ts +149 -0
  23. package/dist/streaming/progress-tracker.d.ts.map +1 -0
  24. package/dist/streaming/progress-tracker.js +519 -0
  25. package/dist/streaming/progress-tracker.js.map +1 -0
  26. package/dist/streaming/session-manager.d.ts +238 -0
  27. package/dist/streaming/session-manager.d.ts.map +1 -0
  28. package/dist/streaming/session-manager.js +546 -0
  29. package/dist/streaming/session-manager.js.map +1 -0
  30. package/dist/streaming/sse-transport.d.ts +95 -0
  31. package/dist/streaming/sse-transport.d.ts.map +1 -0
  32. package/dist/streaming/sse-transport.js +319 -0
  33. package/dist/streaming/sse-transport.js.map +1 -0
  34. package/dist/streaming/streaming-orchestrator.d.ts +153 -0
  35. package/dist/streaming/streaming-orchestrator.d.ts.map +1 -0
  36. package/dist/streaming/streaming-orchestrator.js +436 -0
  37. package/dist/streaming/streaming-orchestrator.js.map +1 -0
  38. package/dist/test-utils/process-manager.d.ts +61 -0
  39. package/dist/test-utils/process-manager.d.ts.map +1 -0
  40. package/dist/test-utils/process-manager.js +262 -0
  41. package/dist/test-utils/process-manager.js.map +1 -0
  42. package/dist/test-utils/server-harness.d.ts +73 -0
  43. package/dist/test-utils/server-harness.d.ts.map +1 -0
  44. package/dist/test-utils/server-harness.js +296 -0
  45. package/dist/test-utils/server-harness.js.map +1 -0
  46. package/dist/test-utils/streaming-fuzz.d.ts +57 -0
  47. package/dist/test-utils/streaming-fuzz.d.ts.map +1 -0
  48. package/dist/test-utils/streaming-fuzz.js +287 -0
  49. package/dist/test-utils/streaming-fuzz.js.map +1 -0
  50. package/dist/test-utils/test-isolation.d.ts +70 -0
  51. package/dist/test-utils/test-isolation.d.ts.map +1 -0
  52. package/dist/test-utils/test-isolation.js +193 -0
  53. package/dist/test-utils/test-isolation.js.map +1 -0
  54. package/dist/tool-definitions.d.ts.map +1 -1
  55. package/dist/tool-definitions.js +12 -6
  56. package/dist/tool-definitions.js.map +1 -1
  57. package/dist/types/brutalist.d.ts +3 -3
  58. package/dist/types/brutalist.d.ts.map +1 -1
  59. package/dist/types/tool-config.d.ts +0 -1
  60. package/dist/types/tool-config.d.ts.map +1 -1
  61. package/dist/types/tool-config.js +0 -1
  62. package/dist/types/tool-config.js.map +1 -1
  63. package/dist/utils/response-cache.d.ts +14 -7
  64. package/dist/utils/response-cache.d.ts.map +1 -1
  65. package/dist/utils/response-cache.js +173 -62
  66. package/dist/utils/response-cache.js.map +1 -1
  67. package/package.json +13 -3
@@ -0,0 +1,141 @@
1
+ import { StreamingEvent } from '../cli-agents.js';
2
+ /**
3
+ * Priority level for events
4
+ */
5
+ export type EventPriority = 'immediate' | 'high' | 'normal' | 'low';
6
+ /**
7
+ * Buffer state for monitoring and metrics
8
+ */
9
+ interface BufferState {
10
+ sessionId: string;
11
+ totalEvents: number;
12
+ pendingEvents: number;
13
+ lastFlush: number;
14
+ flushCount: number;
15
+ backpressure: boolean;
16
+ memoryUsage: number;
17
+ }
18
+ /**
19
+ * Event batch for delivery
20
+ */
21
+ export interface EventBatch {
22
+ sessionId: string;
23
+ events: StreamingEvent[];
24
+ priority: EventPriority;
25
+ batchId: string;
26
+ createdAt: number;
27
+ }
28
+ /**
29
+ * Intelligent buffering system with adaptive throttling and content-aware batching
30
+ *
31
+ * Features:
32
+ * - Priority-based queuing with immediate delivery for critical events
33
+ * - Adaptive throttling based on content type and system load
34
+ * - Content coalescence to reduce noise
35
+ * - Memory-bounded circular buffers
36
+ * - Backpressure handling
37
+ * - Real-time metrics and monitoring
38
+ */
39
+ export declare class IntelligentBuffer {
40
+ private buffers;
41
+ private flushTimers;
42
+ private states;
43
+ private backlog;
44
+ private readonly BUFFERING_RULES;
45
+ private readonly DEFAULT_RULE;
46
+ private readonly MAX_BUFFER_SIZE;
47
+ private readonly MAX_MEMORY_MB;
48
+ private readonly BACKLOG_SIZE;
49
+ constructor();
50
+ /**
51
+ * Add event to buffer with intelligent routing
52
+ */
53
+ add(event: StreamingEvent): void;
54
+ /**
55
+ * Force flush of all pending events for a session
56
+ */
57
+ flush(sessionId: string, priority?: EventPriority): EventBatch | null;
58
+ /**
59
+ * Get backlog events for late subscribers
60
+ */
61
+ getBacklog(sessionId: string, limit?: number): StreamingEvent[];
62
+ /**
63
+ * Get buffer state for monitoring
64
+ */
65
+ getState(sessionId: string): BufferState | null;
66
+ /**
67
+ * Get all active sessions
68
+ */
69
+ getActiveSessions(): string[];
70
+ /**
71
+ * Cleanup session resources
72
+ */
73
+ cleanup(sessionId: string): void;
74
+ /**
75
+ * Get total memory usage across all sessions
76
+ */
77
+ getTotalMemoryUsage(): number;
78
+ /**
79
+ * Get system-wide metrics
80
+ */
81
+ getMetrics(): {
82
+ activeSessions: number;
83
+ totalEvents: number;
84
+ totalMemoryMB: number;
85
+ backpressureSessions: number;
86
+ };
87
+ /**
88
+ * Classify event for buffering rules
89
+ */
90
+ private classifyEvent;
91
+ /**
92
+ * Get event priority for backpressure handling
93
+ */
94
+ private getEventPriority;
95
+ /**
96
+ * Schedule flush based on buffering rule
97
+ */
98
+ private scheduleFlush;
99
+ /**
100
+ * Apply content coalescence to reduce noise
101
+ */
102
+ private applyCoalescence;
103
+ /**
104
+ * Generate coalescence key for grouping similar events
105
+ */
106
+ private getCoalescenceKey;
107
+ /**
108
+ * Merge similar events into single event
109
+ */
110
+ private mergeEvents;
111
+ /**
112
+ * Get or create buffer for session
113
+ */
114
+ private getOrCreateBuffer;
115
+ /**
116
+ * Get or create buffer state
117
+ */
118
+ private getOrCreateState;
119
+ /**
120
+ * Add event to backlog for late subscribers
121
+ */
122
+ private addToBacklog;
123
+ /**
124
+ * Estimate memory usage for session
125
+ */
126
+ private estimateMemoryUsage;
127
+ /**
128
+ * Handle memory pressure by dropping low priority events
129
+ */
130
+ private handleMemoryPressure;
131
+ /**
132
+ * Clean up stale sessions (no activity for > 1 hour)
133
+ */
134
+ private cleanupStaleSessions;
135
+ /**
136
+ * Deliver batch to subscribers (to be implemented by transport layer)
137
+ */
138
+ private deliverBatch;
139
+ }
140
+ export {};
141
+ //# sourceMappingURL=intelligent-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intelligent-buffer.d.ts","sourceRoot":"","sources":["../../src/streaming/intelligent-buffer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAyIpE;;GAEG;AACH,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAoD;IACnE,OAAO,CAAC,WAAW,CAAqC;IACxD,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,OAAO,CAAqD;IAGpE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAgE9B;IAEF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAK3B;IAGF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAM;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAO;;IAOpC;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IA8ChC;;OAEG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,UAAU,GAAG,IAAI;IAyCrE;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE;IAS/D;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAI/C;;OAEG;IACH,iBAAiB,IAAI,MAAM,EAAE;IAI7B;;OAEG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAmBhC;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAK7B;;OAEG;IACH,UAAU,IAAI;QACZ,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,oBAAoB,EAAE,MAAM,CAAC;KAC9B;IAWD;;OAEG;IACH,OAAO,CAAC,aAAa;IAwCrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAoCrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA8BxB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAkBnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,YAAY;IAUpB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
@@ -0,0 +1,555 @@
1
+ import { logger } from '../logger.js';
2
+ /**
3
+ * Circular buffer for memory-efficient event storage
4
+ */
5
+ class CircularBuffer {
6
+ buffer;
7
+ size;
8
+ head = 0;
9
+ tail = 0;
10
+ count = 0;
11
+ constructor(capacity) {
12
+ this.size = capacity;
13
+ this.buffer = new Array(capacity);
14
+ }
15
+ add(item) {
16
+ this.buffer[this.tail] = item;
17
+ this.tail = (this.tail + 1) % this.size;
18
+ if (this.count < this.size) {
19
+ this.count++;
20
+ }
21
+ else {
22
+ // Buffer full, move head
23
+ this.head = (this.head + 1) % this.size;
24
+ }
25
+ }
26
+ flush() {
27
+ const items = [];
28
+ while (this.count > 0) {
29
+ items.push(this.buffer[this.head]);
30
+ this.head = (this.head + 1) % this.size;
31
+ this.count--;
32
+ }
33
+ return items;
34
+ }
35
+ peek(count = this.count) {
36
+ const items = [];
37
+ let current = this.head;
38
+ for (let i = 0; i < Math.min(count, this.count); i++) {
39
+ items.push(this.buffer[current]);
40
+ current = (current + 1) % this.size;
41
+ }
42
+ return items;
43
+ }
44
+ getCount() {
45
+ return this.count;
46
+ }
47
+ isFull() {
48
+ return this.count === this.size;
49
+ }
50
+ }
51
+ /**
52
+ * Priority queue implementation for event batching
53
+ */
54
+ class PriorityQueue {
55
+ queues = new Map();
56
+ priorities = ['immediate', 'high', 'normal', 'low'];
57
+ constructor() {
58
+ this.priorities.forEach(priority => {
59
+ this.queues.set(priority, []);
60
+ });
61
+ }
62
+ enqueue(item, priority) {
63
+ const queue = this.queues.get(priority);
64
+ if (queue) {
65
+ queue.push(item);
66
+ }
67
+ }
68
+ dequeue() {
69
+ for (const priority of this.priorities) {
70
+ const queue = this.queues.get(priority);
71
+ if (queue && queue.length > 0) {
72
+ const item = queue.shift();
73
+ return { item, priority };
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ dequeueAll(priority) {
79
+ if (priority) {
80
+ const queue = this.queues.get(priority);
81
+ if (queue) {
82
+ const items = [...queue];
83
+ queue.length = 0;
84
+ return items;
85
+ }
86
+ return [];
87
+ }
88
+ // Dequeue all items in priority order
89
+ const items = [];
90
+ for (const p of this.priorities) {
91
+ const queue = this.queues.get(p);
92
+ if (queue) {
93
+ items.push(...queue);
94
+ queue.length = 0;
95
+ }
96
+ }
97
+ return items;
98
+ }
99
+ size(priority) {
100
+ if (priority) {
101
+ return this.queues.get(priority)?.length || 0;
102
+ }
103
+ return Array.from(this.queues.values()).reduce((sum, queue) => sum + queue.length, 0);
104
+ }
105
+ isEmpty() {
106
+ return this.size() === 0;
107
+ }
108
+ }
109
+ /**
110
+ * Intelligent buffering system with adaptive throttling and content-aware batching
111
+ *
112
+ * Features:
113
+ * - Priority-based queuing with immediate delivery for critical events
114
+ * - Adaptive throttling based on content type and system load
115
+ * - Content coalescence to reduce noise
116
+ * - Memory-bounded circular buffers
117
+ * - Backpressure handling
118
+ * - Real-time metrics and monitoring
119
+ */
120
+ export class IntelligentBuffer {
121
+ buffers = new Map();
122
+ flushTimers = new Map();
123
+ states = new Map();
124
+ backlog = new Map();
125
+ // Adaptive buffering rules based on content analysis
126
+ BUFFERING_RULES = {
127
+ // Critical findings bypass buffering
128
+ 'critical_finding': {
129
+ delay: 0,
130
+ maxBatch: 1,
131
+ coalescence: false,
132
+ priority: 'immediate'
133
+ },
134
+ // Security issues get high priority
135
+ 'security_finding': {
136
+ delay: 50,
137
+ maxBatch: 2,
138
+ coalescence: false,
139
+ priority: 'high'
140
+ },
141
+ // Regular findings with moderate batching
142
+ 'finding': {
143
+ delay: 200,
144
+ maxBatch: 5,
145
+ coalescence: true,
146
+ priority: 'normal'
147
+ },
148
+ // Progress updates with standard throttling
149
+ 'agent_progress': {
150
+ delay: 200,
151
+ maxBatch: 10,
152
+ coalescence: true,
153
+ priority: 'normal'
154
+ },
155
+ // Errors need immediate attention
156
+ 'agent_error': {
157
+ delay: 0,
158
+ maxBatch: 1,
159
+ coalescence: false,
160
+ priority: 'immediate'
161
+ },
162
+ // Completion events are high priority
163
+ 'agent_complete': {
164
+ delay: 100,
165
+ maxBatch: 1,
166
+ coalescence: false,
167
+ priority: 'high'
168
+ },
169
+ // Debug info can be heavily throttled
170
+ 'debug_info': {
171
+ delay: 1000,
172
+ maxBatch: 20,
173
+ coalescence: true,
174
+ priority: 'low'
175
+ },
176
+ // Milestones are important but can be batched
177
+ 'milestone': {
178
+ delay: 150,
179
+ maxBatch: 3,
180
+ coalescence: false,
181
+ priority: 'high'
182
+ }
183
+ };
184
+ DEFAULT_RULE = {
185
+ delay: 500,
186
+ maxBatch: 5,
187
+ coalescence: true,
188
+ priority: 'normal'
189
+ };
190
+ // Configuration
191
+ MAX_BUFFER_SIZE = 1000; // Events per session
192
+ MAX_MEMORY_MB = 50; // Memory limit per session
193
+ BACKLOG_SIZE = 500; // Backlog events for late subscribers
194
+ constructor() {
195
+ // Periodic cleanup of stale sessions
196
+ setInterval(() => this.cleanupStaleSessions(), 60000); // Every minute
197
+ }
198
+ /**
199
+ * Add event to buffer with intelligent routing
200
+ */
201
+ add(event) {
202
+ if (!event.sessionId) {
203
+ logger.warn('Event without sessionId dropped');
204
+ return;
205
+ }
206
+ // Get or create buffer state
207
+ const state = this.getOrCreateState(event.sessionId);
208
+ // Check for backpressure
209
+ if (state.backpressure) {
210
+ logger.warn(`Backpressure detected for session ${event.sessionId}, dropping low priority events`);
211
+ if (this.getEventPriority(event) === 'low') {
212
+ return;
213
+ }
214
+ }
215
+ // Classify event and get buffering rule
216
+ const classification = this.classifyEvent(event);
217
+ const rule = this.BUFFERING_RULES[classification] || this.DEFAULT_RULE;
218
+ // Get or create buffer
219
+ const buffer = this.getOrCreateBuffer(event.sessionId);
220
+ // Add to backlog for late subscribers
221
+ this.addToBacklog(event);
222
+ // Enqueue with priority
223
+ buffer.enqueue(event, rule.priority);
224
+ // Update state
225
+ state.totalEvents++;
226
+ state.pendingEvents = buffer.size();
227
+ state.memoryUsage = this.estimateMemoryUsage(event.sessionId);
228
+ // Check memory limits
229
+ if (state.memoryUsage > this.MAX_MEMORY_MB * 1024 * 1024) {
230
+ this.handleMemoryPressure(event.sessionId);
231
+ }
232
+ // Schedule flush based on rule
233
+ this.scheduleFlush(event.sessionId, rule);
234
+ logger.debug(`Buffered ${classification} event for session ${event.sessionId} (${state.pendingEvents} pending)`);
235
+ }
236
+ /**
237
+ * Force flush of all pending events for a session
238
+ */
239
+ flush(sessionId, priority) {
240
+ const buffer = this.buffers.get(sessionId);
241
+ const state = this.states.get(sessionId);
242
+ if (!buffer || !state) {
243
+ return null;
244
+ }
245
+ const events = buffer.dequeueAll(priority);
246
+ if (events.length === 0) {
247
+ return null;
248
+ }
249
+ // Apply coalescence if enabled
250
+ const coalescedEvents = this.applyCoalescence(events);
251
+ // Update state
252
+ state.pendingEvents = buffer.size();
253
+ state.lastFlush = Date.now();
254
+ state.flushCount++;
255
+ // Clear timer if exists
256
+ const timer = this.flushTimers.get(sessionId);
257
+ if (timer) {
258
+ clearTimeout(timer);
259
+ this.flushTimers.delete(sessionId);
260
+ }
261
+ const batch = {
262
+ sessionId,
263
+ events: coalescedEvents,
264
+ priority: priority || 'normal',
265
+ batchId: `${sessionId}-${state.flushCount}-${Date.now()}`,
266
+ createdAt: Date.now()
267
+ };
268
+ logger.debug(`Flushed ${coalescedEvents.length} events for session ${sessionId} (batch: ${batch.batchId})`);
269
+ return batch;
270
+ }
271
+ /**
272
+ * Get backlog events for late subscribers
273
+ */
274
+ getBacklog(sessionId, limit) {
275
+ const backlog = this.backlog.get(sessionId);
276
+ if (!backlog) {
277
+ return [];
278
+ }
279
+ return backlog.peek(limit);
280
+ }
281
+ /**
282
+ * Get buffer state for monitoring
283
+ */
284
+ getState(sessionId) {
285
+ return this.states.get(sessionId) || null;
286
+ }
287
+ /**
288
+ * Get all active sessions
289
+ */
290
+ getActiveSessions() {
291
+ return Array.from(this.buffers.keys());
292
+ }
293
+ /**
294
+ * Cleanup session resources
295
+ */
296
+ cleanup(sessionId) {
297
+ // Flush any remaining events
298
+ this.flush(sessionId);
299
+ // Clear timer
300
+ const timer = this.flushTimers.get(sessionId);
301
+ if (timer) {
302
+ clearTimeout(timer);
303
+ this.flushTimers.delete(sessionId);
304
+ }
305
+ // Remove all resources
306
+ this.buffers.delete(sessionId);
307
+ this.states.delete(sessionId);
308
+ this.backlog.delete(sessionId);
309
+ logger.info(`Cleaned up buffer resources for session ${sessionId}`);
310
+ }
311
+ /**
312
+ * Get total memory usage across all sessions
313
+ */
314
+ getTotalMemoryUsage() {
315
+ return Array.from(this.states.values())
316
+ .reduce((sum, state) => sum + state.memoryUsage, 0);
317
+ }
318
+ /**
319
+ * Get system-wide metrics
320
+ */
321
+ getMetrics() {
322
+ const states = Array.from(this.states.values());
323
+ return {
324
+ activeSessions: states.length,
325
+ totalEvents: states.reduce((sum, s) => sum + s.totalEvents, 0),
326
+ totalMemoryMB: Math.round(this.getTotalMemoryUsage() / (1024 * 1024)),
327
+ backpressureSessions: states.filter(s => s.backpressure).length
328
+ };
329
+ }
330
+ /**
331
+ * Classify event for buffering rules
332
+ */
333
+ classifyEvent(event) {
334
+ const content = event.content?.toLowerCase() || '';
335
+ const metadata = event.metadata;
336
+ // Check metadata classification first
337
+ if (metadata?.contentType === 'finding') {
338
+ if (metadata.severity === 'critical') {
339
+ return 'critical_finding';
340
+ }
341
+ if (content.includes('security') || content.includes('vulnerability')) {
342
+ return 'security_finding';
343
+ }
344
+ return 'finding';
345
+ }
346
+ // Event type classification
347
+ if (event.type === 'agent_error') {
348
+ return 'agent_error';
349
+ }
350
+ if (event.type === 'agent_complete') {
351
+ return 'agent_complete';
352
+ }
353
+ // Content-based classification
354
+ if (content.includes('critical') || content.includes('security')) {
355
+ return 'critical_finding';
356
+ }
357
+ if (metadata?.contentType === 'milestone') {
358
+ return 'milestone';
359
+ }
360
+ if (metadata?.contentType === 'debug') {
361
+ return 'debug_info';
362
+ }
363
+ return 'agent_progress';
364
+ }
365
+ /**
366
+ * Get event priority for backpressure handling
367
+ */
368
+ getEventPriority(event) {
369
+ const classification = this.classifyEvent(event);
370
+ return this.BUFFERING_RULES[classification]?.priority || 'normal';
371
+ }
372
+ /**
373
+ * Schedule flush based on buffering rule
374
+ */
375
+ scheduleFlush(sessionId, rule) {
376
+ // Immediate delivery
377
+ if (rule.delay === 0) {
378
+ setImmediate(() => {
379
+ const batch = this.flush(sessionId, rule.priority);
380
+ if (batch) {
381
+ this.deliverBatch(batch);
382
+ }
383
+ });
384
+ return;
385
+ }
386
+ // Check if we should flush due to batch size
387
+ const buffer = this.buffers.get(sessionId);
388
+ if (buffer && buffer.size(rule.priority) >= rule.maxBatch) {
389
+ const batch = this.flush(sessionId, rule.priority);
390
+ if (batch) {
391
+ this.deliverBatch(batch);
392
+ }
393
+ return;
394
+ }
395
+ // Schedule timer if not already scheduled
396
+ if (!this.flushTimers.has(sessionId)) {
397
+ const timer = setTimeout(() => {
398
+ this.flushTimers.delete(sessionId);
399
+ const batch = this.flush(sessionId);
400
+ if (batch) {
401
+ this.deliverBatch(batch);
402
+ }
403
+ }, rule.delay);
404
+ this.flushTimers.set(sessionId, timer);
405
+ }
406
+ }
407
+ /**
408
+ * Apply content coalescence to reduce noise
409
+ */
410
+ applyCoalescence(events) {
411
+ if (events.length <= 1) {
412
+ return events;
413
+ }
414
+ const coalesced = [];
415
+ const groups = new Map();
416
+ // Group similar events
417
+ for (const event of events) {
418
+ const key = this.getCoalescenceKey(event);
419
+ if (!groups.has(key)) {
420
+ groups.set(key, []);
421
+ }
422
+ groups.get(key).push(event);
423
+ }
424
+ // Coalesce each group
425
+ for (const group of groups.values()) {
426
+ if (group.length === 1) {
427
+ coalesced.push(group[0]);
428
+ }
429
+ else {
430
+ const merged = this.mergeEvents(group);
431
+ coalesced.push(merged);
432
+ }
433
+ }
434
+ return coalesced.sort((a, b) => a.timestamp - b.timestamp);
435
+ }
436
+ /**
437
+ * Generate coalescence key for grouping similar events
438
+ */
439
+ getCoalescenceKey(event) {
440
+ return `${event.agent}-${event.type}-${event.metadata?.contentType || 'default'}`;
441
+ }
442
+ /**
443
+ * Merge similar events into single event
444
+ */
445
+ mergeEvents(events) {
446
+ const first = events[0];
447
+ const last = events[events.length - 1];
448
+ return {
449
+ ...first,
450
+ content: events.length > 3
451
+ ? `${first.content} ... [${events.length - 2} similar events] ... ${last.content}`
452
+ : events.map(e => e.content).join(' | '),
453
+ timestamp: last.timestamp,
454
+ metadata: {
455
+ ...first.metadata,
456
+ coalescedCount: events.length,
457
+ timespan: last.timestamp - first.timestamp
458
+ }
459
+ };
460
+ }
461
+ /**
462
+ * Get or create buffer for session
463
+ */
464
+ getOrCreateBuffer(sessionId) {
465
+ if (!this.buffers.has(sessionId)) {
466
+ this.buffers.set(sessionId, new PriorityQueue());
467
+ }
468
+ return this.buffers.get(sessionId);
469
+ }
470
+ /**
471
+ * Get or create buffer state
472
+ */
473
+ getOrCreateState(sessionId) {
474
+ if (!this.states.has(sessionId)) {
475
+ this.states.set(sessionId, {
476
+ sessionId,
477
+ totalEvents: 0,
478
+ pendingEvents: 0,
479
+ lastFlush: Date.now(),
480
+ flushCount: 0,
481
+ backpressure: false,
482
+ memoryUsage: 0
483
+ });
484
+ }
485
+ return this.states.get(sessionId);
486
+ }
487
+ /**
488
+ * Add event to backlog for late subscribers
489
+ */
490
+ addToBacklog(event) {
491
+ if (!event.sessionId)
492
+ return;
493
+ if (!this.backlog.has(event.sessionId)) {
494
+ this.backlog.set(event.sessionId, new CircularBuffer(this.BACKLOG_SIZE));
495
+ }
496
+ this.backlog.get(event.sessionId).add(event);
497
+ }
498
+ /**
499
+ * Estimate memory usage for session
500
+ */
501
+ estimateMemoryUsage(sessionId) {
502
+ const buffer = this.buffers.get(sessionId);
503
+ const backlog = this.backlog.get(sessionId);
504
+ let size = 0;
505
+ // Estimate buffer size (rough approximation)
506
+ if (buffer) {
507
+ size += buffer.size() * 500; // ~500 bytes per event
508
+ }
509
+ // Estimate backlog size
510
+ if (backlog) {
511
+ size += backlog.getCount() * 500;
512
+ }
513
+ return size;
514
+ }
515
+ /**
516
+ * Handle memory pressure by dropping low priority events
517
+ */
518
+ handleMemoryPressure(sessionId) {
519
+ const state = this.getOrCreateState(sessionId);
520
+ const buffer = this.buffers.get(sessionId);
521
+ state.backpressure = true;
522
+ // Flush low priority events immediately to free memory
523
+ if (buffer) {
524
+ const lowPriorityEvents = buffer.dequeueAll('low');
525
+ logger.warn(`Memory pressure: dropped ${lowPriorityEvents.length} low priority events for session ${sessionId}`);
526
+ }
527
+ // Clear backpressure after a delay
528
+ setTimeout(() => {
529
+ state.backpressure = false;
530
+ }, 5000);
531
+ }
532
+ /**
533
+ * Clean up stale sessions (no activity for > 1 hour)
534
+ */
535
+ cleanupStaleSessions() {
536
+ const now = Date.now();
537
+ const staleThreshold = 60 * 60 * 1000; // 1 hour
538
+ for (const [sessionId, state] of this.states.entries()) {
539
+ if (now - state.lastFlush > staleThreshold) {
540
+ logger.info(`Cleaning up stale session: ${sessionId}`);
541
+ this.cleanup(sessionId);
542
+ }
543
+ }
544
+ }
545
+ /**
546
+ * Deliver batch to subscribers (to be implemented by transport layer)
547
+ */
548
+ deliverBatch(batch) {
549
+ // This will be called by the transport layer
550
+ // For now, just emit a custom event that can be listened to
551
+ // Emit batch as event (commented out as it's not needed for current implementation)
552
+ // process.emit('streaming-batch', batch);
553
+ }
554
+ }
555
+ //# sourceMappingURL=intelligent-buffer.js.map