@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.
- package/README.md +3 -1
- package/dist/brutalist-server.d.ts +5 -0
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +244 -80
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +7 -3
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +307 -48
- package/dist/cli-agents.js.map +1 -1
- package/dist/streaming/circuit-breaker.d.ts +186 -0
- package/dist/streaming/circuit-breaker.d.ts.map +1 -0
- package/dist/streaming/circuit-breaker.js +463 -0
- package/dist/streaming/circuit-breaker.js.map +1 -0
- package/dist/streaming/intelligent-buffer.d.ts +141 -0
- package/dist/streaming/intelligent-buffer.d.ts.map +1 -0
- package/dist/streaming/intelligent-buffer.js +555 -0
- package/dist/streaming/intelligent-buffer.js.map +1 -0
- package/dist/streaming/output-parser.d.ts +89 -0
- package/dist/streaming/output-parser.d.ts.map +1 -0
- package/dist/streaming/output-parser.js +349 -0
- package/dist/streaming/output-parser.js.map +1 -0
- package/dist/streaming/progress-tracker.d.ts +149 -0
- package/dist/streaming/progress-tracker.d.ts.map +1 -0
- package/dist/streaming/progress-tracker.js +519 -0
- package/dist/streaming/progress-tracker.js.map +1 -0
- package/dist/streaming/session-manager.d.ts +238 -0
- package/dist/streaming/session-manager.d.ts.map +1 -0
- package/dist/streaming/session-manager.js +546 -0
- package/dist/streaming/session-manager.js.map +1 -0
- package/dist/streaming/sse-transport.d.ts +95 -0
- package/dist/streaming/sse-transport.d.ts.map +1 -0
- package/dist/streaming/sse-transport.js +319 -0
- package/dist/streaming/sse-transport.js.map +1 -0
- package/dist/streaming/streaming-orchestrator.d.ts +153 -0
- package/dist/streaming/streaming-orchestrator.d.ts.map +1 -0
- package/dist/streaming/streaming-orchestrator.js +436 -0
- package/dist/streaming/streaming-orchestrator.js.map +1 -0
- package/dist/test-utils/process-manager.d.ts +61 -0
- package/dist/test-utils/process-manager.d.ts.map +1 -0
- package/dist/test-utils/process-manager.js +262 -0
- package/dist/test-utils/process-manager.js.map +1 -0
- package/dist/test-utils/server-harness.d.ts +73 -0
- package/dist/test-utils/server-harness.d.ts.map +1 -0
- package/dist/test-utils/server-harness.js +296 -0
- package/dist/test-utils/server-harness.js.map +1 -0
- package/dist/test-utils/streaming-fuzz.d.ts +57 -0
- package/dist/test-utils/streaming-fuzz.d.ts.map +1 -0
- package/dist/test-utils/streaming-fuzz.js +287 -0
- package/dist/test-utils/streaming-fuzz.js.map +1 -0
- package/dist/test-utils/test-isolation.d.ts +70 -0
- package/dist/test-utils/test-isolation.d.ts.map +1 -0
- package/dist/test-utils/test-isolation.js +193 -0
- package/dist/test-utils/test-isolation.js.map +1 -0
- package/dist/tool-definitions.d.ts.map +1 -1
- package/dist/tool-definitions.js +12 -6
- package/dist/tool-definitions.js.map +1 -1
- package/dist/types/brutalist.d.ts +3 -3
- package/dist/types/brutalist.d.ts.map +1 -1
- package/dist/types/tool-config.d.ts +0 -1
- package/dist/types/tool-config.d.ts.map +1 -1
- package/dist/types/tool-config.js +0 -1
- package/dist/types/tool-config.js.map +1 -1
- package/dist/utils/response-cache.d.ts +14 -7
- package/dist/utils/response-cache.d.ts.map +1 -1
- package/dist/utils/response-cache.js +173 -62
- package/dist/utils/response-cache.js.map +1 -1
- 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
|