@iflow-mcp/ejmockler-brutalist
Version:
Deploy Claude, Codex & Gemini CLI agents to demolish your work before users do. Real file analysis. Brutal honesty. Now with conversation continuation & intelligent pagination.
557 lines • 17.6 kB
JavaScript
import { logger } from '../logger.js';
/**
* Circular buffer for memory-efficient event storage
*/
class CircularBuffer {
buffer;
size;
head = 0;
tail = 0;
count = 0;
constructor(capacity) {
this.size = capacity;
this.buffer = new Array(capacity);
}
add(item) {
this.buffer[this.tail] = item;
this.tail = (this.tail + 1) % this.size;
if (this.count < this.size) {
this.count++;
}
else {
// Buffer full, move head
this.head = (this.head + 1) % this.size;
}
}
flush() {
const items = [];
while (this.count > 0) {
items.push(this.buffer[this.head]);
this.head = (this.head + 1) % this.size;
this.count--;
}
return items;
}
peek(count = this.count) {
const items = [];
let current = this.head;
for (let i = 0; i < Math.min(count, this.count); i++) {
items.push(this.buffer[current]);
current = (current + 1) % this.size;
}
return items;
}
getCount() {
return this.count;
}
isFull() {
return this.count === this.size;
}
}
/**
* Priority queue implementation for event batching
*/
class PriorityQueue {
queues = new Map();
priorities = ['immediate', 'high', 'normal', 'low'];
constructor() {
this.priorities.forEach(priority => {
this.queues.set(priority, []);
});
}
enqueue(item, priority) {
const queue = this.queues.get(priority);
if (queue) {
queue.push(item);
}
}
dequeue() {
for (const priority of this.priorities) {
const queue = this.queues.get(priority);
if (queue && queue.length > 0) {
const item = queue.shift();
return { item, priority };
}
}
return null;
}
dequeueAll(priority) {
if (priority) {
const queue = this.queues.get(priority);
if (queue) {
const items = [...queue];
queue.length = 0;
return items;
}
return [];
}
// Dequeue all items in priority order
const items = [];
for (const p of this.priorities) {
const queue = this.queues.get(p);
if (queue) {
items.push(...queue);
queue.length = 0;
}
}
return items;
}
size(priority) {
if (priority) {
return this.queues.get(priority)?.length || 0;
}
return Array.from(this.queues.values()).reduce((sum, queue) => sum + queue.length, 0);
}
isEmpty() {
return this.size() === 0;
}
}
/**
* Intelligent buffering system with adaptive throttling and content-aware batching
*
* Features:
* - Priority-based queuing with immediate delivery for critical events
* - Adaptive throttling based on content type and system load
* - Content coalescence to reduce noise
* - Memory-bounded circular buffers
* - Backpressure handling
* - Real-time metrics and monitoring
*/
export class IntelligentBuffer {
buffers = new Map();
flushTimers = new Map();
states = new Map();
backlog = new Map();
// Adaptive buffering rules based on content analysis
BUFFERING_RULES = {
// Critical findings bypass buffering
'critical_finding': {
delay: 0,
maxBatch: 1,
coalescence: false,
priority: 'immediate'
},
// Security issues get high priority
'security_finding': {
delay: 50,
maxBatch: 2,
coalescence: false,
priority: 'high'
},
// Regular findings with moderate batching
'finding': {
delay: 200,
maxBatch: 5,
coalescence: true,
priority: 'normal'
},
// Progress updates with standard throttling
'agent_progress': {
delay: 200,
maxBatch: 10,
coalescence: true,
priority: 'normal'
},
// Errors need immediate attention
'agent_error': {
delay: 0,
maxBatch: 1,
coalescence: false,
priority: 'immediate'
},
// Completion events are high priority
'agent_complete': {
delay: 100,
maxBatch: 1,
coalescence: false,
priority: 'high'
},
// Debug info can be heavily throttled
'debug_info': {
delay: 1000,
maxBatch: 20,
coalescence: true,
priority: 'low'
},
// Milestones are important but can be batched
'milestone': {
delay: 150,
maxBatch: 3,
coalescence: false,
priority: 'high'
}
};
DEFAULT_RULE = {
delay: 500,
maxBatch: 5,
coalescence: true,
priority: 'normal'
};
// Configuration
MAX_BUFFER_SIZE = 1000; // Events per session
MAX_MEMORY_MB = 50; // Memory limit per session
BACKLOG_SIZE = 500; // Backlog events for late subscribers
constructor() {
// Periodic cleanup of stale sessions
const cleanupInterval = setInterval(() => this.cleanupStaleSessions(), 60000); // Every minute
// Allow Node.js to exit if this is the only active timer
cleanupInterval.unref();
}
/**
* Add event to buffer with intelligent routing
*/
add(event) {
if (!event.sessionId) {
logger.warn('Event without sessionId dropped');
return;
}
// Get or create buffer state
const state = this.getOrCreateState(event.sessionId);
// Check for backpressure
if (state.backpressure) {
logger.warn(`Backpressure detected for session ${event.sessionId}, dropping low priority events`);
if (this.getEventPriority(event) === 'low') {
return;
}
}
// Classify event and get buffering rule
const classification = this.classifyEvent(event);
const rule = this.BUFFERING_RULES[classification] || this.DEFAULT_RULE;
// Get or create buffer
const buffer = this.getOrCreateBuffer(event.sessionId);
// Add to backlog for late subscribers
this.addToBacklog(event);
// Enqueue with priority
buffer.enqueue(event, rule.priority);
// Update state
state.totalEvents++;
state.pendingEvents = buffer.size();
state.memoryUsage = this.estimateMemoryUsage(event.sessionId);
// Check memory limits
if (state.memoryUsage > this.MAX_MEMORY_MB * 1024 * 1024) {
this.handleMemoryPressure(event.sessionId);
}
// Schedule flush based on rule
this.scheduleFlush(event.sessionId, rule);
logger.debug(`Buffered ${classification} event for session ${event.sessionId} (${state.pendingEvents} pending)`);
}
/**
* Force flush of all pending events for a session
*/
flush(sessionId, priority) {
const buffer = this.buffers.get(sessionId);
const state = this.states.get(sessionId);
if (!buffer || !state) {
return null;
}
const events = buffer.dequeueAll(priority);
if (events.length === 0) {
return null;
}
// Apply coalescence if enabled
const coalescedEvents = this.applyCoalescence(events);
// Update state
state.pendingEvents = buffer.size();
state.lastFlush = Date.now();
state.flushCount++;
// Clear timer if exists
const timer = this.flushTimers.get(sessionId);
if (timer) {
clearTimeout(timer);
this.flushTimers.delete(sessionId);
}
const batch = {
sessionId,
events: coalescedEvents,
priority: priority || 'normal',
batchId: `${sessionId}-${state.flushCount}-${Date.now()}`,
createdAt: Date.now()
};
logger.debug(`Flushed ${coalescedEvents.length} events for session ${sessionId} (batch: ${batch.batchId})`);
return batch;
}
/**
* Get backlog events for late subscribers
*/
getBacklog(sessionId, limit) {
const backlog = this.backlog.get(sessionId);
if (!backlog) {
return [];
}
return backlog.peek(limit);
}
/**
* Get buffer state for monitoring
*/
getState(sessionId) {
return this.states.get(sessionId) || null;
}
/**
* Get all active sessions
*/
getActiveSessions() {
return Array.from(this.buffers.keys());
}
/**
* Cleanup session resources
*/
cleanup(sessionId) {
// Flush any remaining events
this.flush(sessionId);
// Clear timer
const timer = this.flushTimers.get(sessionId);
if (timer) {
clearTimeout(timer);
this.flushTimers.delete(sessionId);
}
// Remove all resources
this.buffers.delete(sessionId);
this.states.delete(sessionId);
this.backlog.delete(sessionId);
logger.info(`Cleaned up buffer resources for session ${sessionId}`);
}
/**
* Get total memory usage across all sessions
*/
getTotalMemoryUsage() {
return Array.from(this.states.values())
.reduce((sum, state) => sum + state.memoryUsage, 0);
}
/**
* Get system-wide metrics
*/
getMetrics() {
const states = Array.from(this.states.values());
return {
activeSessions: states.length,
totalEvents: states.reduce((sum, s) => sum + s.totalEvents, 0),
totalMemoryMB: Math.round(this.getTotalMemoryUsage() / (1024 * 1024)),
backpressureSessions: states.filter(s => s.backpressure).length
};
}
/**
* Classify event for buffering rules
*/
classifyEvent(event) {
const content = event.content?.toLowerCase() || '';
const metadata = event.metadata;
// Check metadata classification first
if (metadata?.contentType === 'finding') {
if (metadata.severity === 'critical') {
return 'critical_finding';
}
if (content.includes('security') || content.includes('vulnerability')) {
return 'security_finding';
}
return 'finding';
}
// Event type classification
if (event.type === 'agent_error') {
return 'agent_error';
}
if (event.type === 'agent_complete') {
return 'agent_complete';
}
// Content-based classification
if (content.includes('critical') || content.includes('security')) {
return 'critical_finding';
}
if (metadata?.contentType === 'milestone') {
return 'milestone';
}
if (metadata?.contentType === 'debug') {
return 'debug_info';
}
return 'agent_progress';
}
/**
* Get event priority for backpressure handling
*/
getEventPriority(event) {
const classification = this.classifyEvent(event);
return this.BUFFERING_RULES[classification]?.priority || 'normal';
}
/**
* Schedule flush based on buffering rule
*/
scheduleFlush(sessionId, rule) {
// Immediate delivery
if (rule.delay === 0) {
setImmediate(() => {
const batch = this.flush(sessionId, rule.priority);
if (batch) {
this.deliverBatch(batch);
}
});
return;
}
// Check if we should flush due to batch size
const buffer = this.buffers.get(sessionId);
if (buffer && buffer.size(rule.priority) >= rule.maxBatch) {
const batch = this.flush(sessionId, rule.priority);
if (batch) {
this.deliverBatch(batch);
}
return;
}
// Schedule timer if not already scheduled
if (!this.flushTimers.has(sessionId)) {
const timer = setTimeout(() => {
this.flushTimers.delete(sessionId);
const batch = this.flush(sessionId);
if (batch) {
this.deliverBatch(batch);
}
}, rule.delay);
this.flushTimers.set(sessionId, timer);
}
}
/**
* Apply content coalescence to reduce noise
*/
applyCoalescence(events) {
if (events.length <= 1) {
return events;
}
const coalesced = [];
const groups = new Map();
// Group similar events
for (const event of events) {
const key = this.getCoalescenceKey(event);
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key).push(event);
}
// Coalesce each group
for (const group of groups.values()) {
if (group.length === 1) {
coalesced.push(group[0]);
}
else {
const merged = this.mergeEvents(group);
coalesced.push(merged);
}
}
return coalesced.sort((a, b) => a.timestamp - b.timestamp);
}
/**
* Generate coalescence key for grouping similar events
*/
getCoalescenceKey(event) {
return `${event.agent}-${event.type}-${event.metadata?.contentType || 'default'}`;
}
/**
* Merge similar events into single event
*/
mergeEvents(events) {
const first = events[0];
const last = events[events.length - 1];
return {
...first,
content: events.length > 3
? `${first.content} ... [${events.length - 2} similar events] ... ${last.content}`
: events.map(e => e.content).join(' | '),
timestamp: last.timestamp,
metadata: {
...first.metadata,
coalescedCount: events.length,
timespan: last.timestamp - first.timestamp
}
};
}
/**
* Get or create buffer for session
*/
getOrCreateBuffer(sessionId) {
if (!this.buffers.has(sessionId)) {
this.buffers.set(sessionId, new PriorityQueue());
}
return this.buffers.get(sessionId);
}
/**
* Get or create buffer state
*/
getOrCreateState(sessionId) {
if (!this.states.has(sessionId)) {
this.states.set(sessionId, {
sessionId,
totalEvents: 0,
pendingEvents: 0,
lastFlush: Date.now(),
flushCount: 0,
backpressure: false,
memoryUsage: 0
});
}
return this.states.get(sessionId);
}
/**
* Add event to backlog for late subscribers
*/
addToBacklog(event) {
if (!event.sessionId)
return;
if (!this.backlog.has(event.sessionId)) {
this.backlog.set(event.sessionId, new CircularBuffer(this.BACKLOG_SIZE));
}
this.backlog.get(event.sessionId).add(event);
}
/**
* Estimate memory usage for session
*/
estimateMemoryUsage(sessionId) {
const buffer = this.buffers.get(sessionId);
const backlog = this.backlog.get(sessionId);
let size = 0;
// Estimate buffer size (rough approximation)
if (buffer) {
size += buffer.size() * 500; // ~500 bytes per event
}
// Estimate backlog size
if (backlog) {
size += backlog.getCount() * 500;
}
return size;
}
/**
* Handle memory pressure by dropping low priority events
*/
handleMemoryPressure(sessionId) {
const state = this.getOrCreateState(sessionId);
const buffer = this.buffers.get(sessionId);
state.backpressure = true;
// Flush low priority events immediately to free memory
if (buffer) {
const lowPriorityEvents = buffer.dequeueAll('low');
logger.warn(`Memory pressure: dropped ${lowPriorityEvents.length} low priority events for session ${sessionId}`);
}
// Clear backpressure after a delay
setTimeout(() => {
state.backpressure = false;
}, 5000);
}
/**
* Clean up stale sessions (no activity for > 1 hour)
*/
cleanupStaleSessions() {
const now = Date.now();
const staleThreshold = 60 * 60 * 1000; // 1 hour
for (const [sessionId, state] of this.states.entries()) {
if (now - state.lastFlush > staleThreshold) {
logger.info(`Cleaning up stale session: ${sessionId}`);
this.cleanup(sessionId);
}
}
}
/**
* Deliver batch to subscribers (to be implemented by transport layer)
*/
deliverBatch(batch) {
// This will be called by the transport layer
// For now, just emit a custom event that can be listened to
// Emit batch as event (commented out as it's not needed for current implementation)
// process.emit('streaming-batch', batch);
}
}
//# sourceMappingURL=intelligent-buffer.js.map