mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
fix + test: address PR recommendations + add comprehensive tests for performance optimizations
This commit is contained in:
517
src/__tests__/main/performance-optimizations.test.ts
Normal file
517
src/__tests__/main/performance-optimizations.test.ts
Normal file
@@ -0,0 +1,517 @@
|
||||
/**
|
||||
* @file performance-optimizations.test.ts
|
||||
* @description Unit tests for performance optimizations in the main process.
|
||||
*
|
||||
* Tests cover:
|
||||
* - Group chat session ID regex patterns
|
||||
* - Buffer operations (O(1) append, correct join)
|
||||
* - Debug logging conditional behavior
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
// ============================================================================
|
||||
// Regex Pattern Tests
|
||||
// ============================================================================
|
||||
// These patterns are used in hot paths (process data handlers) and are
|
||||
// pre-compiled at module level for performance. We test them here to ensure
|
||||
// they match the expected session ID formats.
|
||||
|
||||
describe('Group Chat Session ID Patterns', () => {
|
||||
// Moderator session patterns
|
||||
const REGEX_MODERATOR_SESSION = /^group-chat-(.+)-moderator-/;
|
||||
const REGEX_MODERATOR_SESSION_TIMESTAMP = /^group-chat-(.+)-moderator-\d+$/;
|
||||
|
||||
// Participant session patterns
|
||||
const REGEX_PARTICIPANT_UUID =
|
||||
/^group-chat-(.+)-participant-(.+)-([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i;
|
||||
const REGEX_PARTICIPANT_TIMESTAMP = /^group-chat-(.+)-participant-(.+)-(\d{13,})$/;
|
||||
const REGEX_PARTICIPANT_FALLBACK = /^group-chat-(.+)-participant-([^-]+)-/;
|
||||
|
||||
// Web broadcast patterns
|
||||
const REGEX_AI_SUFFIX = /-ai-[^-]+$/;
|
||||
const REGEX_AI_TAB_ID = /-ai-([^-]+)$/;
|
||||
|
||||
describe('REGEX_MODERATOR_SESSION', () => {
|
||||
it('should match moderator session IDs', () => {
|
||||
const sessionId = 'group-chat-test-chat-123-moderator-1705678901234';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('test-chat-123');
|
||||
});
|
||||
|
||||
it('should match moderator synthesis session IDs', () => {
|
||||
const sessionId = 'group-chat-my-chat-moderator-synthesis-1705678901234';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('my-chat');
|
||||
});
|
||||
|
||||
it('should not match participant session IDs', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent1-1705678901234';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
|
||||
it('should not match regular session IDs', () => {
|
||||
const sessionId = 'session-123-ai-tab1';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('REGEX_MODERATOR_SESSION_TIMESTAMP', () => {
|
||||
it('should match moderator session IDs with timestamp suffix', () => {
|
||||
const sessionId = 'group-chat-test-chat-moderator-1705678901234';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION_TIMESTAMP);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('test-chat');
|
||||
});
|
||||
|
||||
it('should not match moderator synthesis session IDs', () => {
|
||||
const sessionId = 'group-chat-my-chat-moderator-synthesis-1705678901234';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION_TIMESTAMP);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
|
||||
it('should not match session IDs without timestamp', () => {
|
||||
const sessionId = 'group-chat-test-chat-moderator-';
|
||||
const match = sessionId.match(REGEX_MODERATOR_SESSION_TIMESTAMP);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('REGEX_PARTICIPANT_UUID', () => {
|
||||
it('should match participant session IDs with UUID suffix', () => {
|
||||
const sessionId =
|
||||
'group-chat-test-chat-participant-Agent1-a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_UUID);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('test-chat');
|
||||
expect(match![2]).toBe('Agent1');
|
||||
expect(match![3]).toBe('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
|
||||
});
|
||||
|
||||
it('should match UUID case-insensitively', () => {
|
||||
const sessionId =
|
||||
'group-chat-test-chat-participant-Agent1-A1B2C3D4-E5F6-7890-ABCD-EF1234567890';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_UUID);
|
||||
expect(match).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not match session IDs with invalid UUID', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent1-invalid-uuid';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_UUID);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle participant names with hyphens', () => {
|
||||
const sessionId =
|
||||
'group-chat-test-chat-participant-My-Agent-Name-a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_UUID);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![2]).toBe('My-Agent-Name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('REGEX_PARTICIPANT_TIMESTAMP', () => {
|
||||
it('should match participant session IDs with 13-digit timestamp', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent1-1705678901234';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_TIMESTAMP);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('test-chat');
|
||||
expect(match![2]).toBe('Agent1');
|
||||
expect(match![3]).toBe('1705678901234');
|
||||
});
|
||||
|
||||
it('should match timestamps with more than 13 digits', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent1-17056789012345';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_TIMESTAMP);
|
||||
expect(match).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not match timestamps with less than 13 digits', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent1-123456789012';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_TIMESTAMP);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle participant names with hyphens', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-My-Agent-1705678901234';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_TIMESTAMP);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![2]).toBe('My-Agent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('REGEX_PARTICIPANT_FALLBACK', () => {
|
||||
it('should match participant session IDs with simple names', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent1-anything';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_FALLBACK);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('test-chat');
|
||||
expect(match![2]).toBe('Agent1');
|
||||
});
|
||||
|
||||
it('should stop at hyphen for participant name', () => {
|
||||
const sessionId = 'group-chat-test-chat-participant-Agent-1-suffix';
|
||||
const match = sessionId.match(REGEX_PARTICIPANT_FALLBACK);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![2]).toBe('Agent'); // Stops at first hyphen after participant name start
|
||||
});
|
||||
});
|
||||
|
||||
describe('REGEX_AI_SUFFIX', () => {
|
||||
it('should match session IDs with -ai- suffix', () => {
|
||||
const sessionId = 'session-123-ai-tab1';
|
||||
expect(REGEX_AI_SUFFIX.test(sessionId)).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove -ai- suffix correctly', () => {
|
||||
const sessionId = 'session-123-ai-tab1';
|
||||
const baseSessionId = sessionId.replace(REGEX_AI_SUFFIX, '');
|
||||
expect(baseSessionId).toBe('session-123');
|
||||
});
|
||||
|
||||
it('should not match session IDs without -ai- suffix', () => {
|
||||
const sessionId = 'session-123-terminal';
|
||||
expect(REGEX_AI_SUFFIX.test(sessionId)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('REGEX_AI_TAB_ID', () => {
|
||||
it('should extract tab ID from session ID', () => {
|
||||
const sessionId = 'session-123-ai-tab1';
|
||||
const match = sessionId.match(REGEX_AI_TAB_ID);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('tab1');
|
||||
});
|
||||
|
||||
it('should handle complex tab IDs', () => {
|
||||
const sessionId = 'session-123-ai-abc123xyz';
|
||||
const match = sessionId.match(REGEX_AI_TAB_ID);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![1]).toBe('abc123xyz');
|
||||
});
|
||||
|
||||
it('should return null for non-AI sessions', () => {
|
||||
const sessionId = 'session-123-terminal';
|
||||
const match = sessionId.match(REGEX_AI_TAB_ID);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Buffer Operation Tests
|
||||
// ============================================================================
|
||||
// These tests verify the O(1) buffer implementation works correctly.
|
||||
|
||||
describe('Group Chat Output Buffer', () => {
|
||||
// Simulate the buffer implementation
|
||||
type BufferEntry = { chunks: string[]; totalLength: number };
|
||||
let buffers: Map<string, BufferEntry>;
|
||||
|
||||
function appendToBuffer(sessionId: string, data: string): number {
|
||||
let buffer = buffers.get(sessionId);
|
||||
if (!buffer) {
|
||||
buffer = { chunks: [], totalLength: 0 };
|
||||
buffers.set(sessionId, buffer);
|
||||
}
|
||||
buffer.chunks.push(data);
|
||||
buffer.totalLength += data.length;
|
||||
return buffer.totalLength;
|
||||
}
|
||||
|
||||
function getBufferedOutput(sessionId: string): string | undefined {
|
||||
const buffer = buffers.get(sessionId);
|
||||
if (!buffer || buffer.chunks.length === 0) return undefined;
|
||||
return buffer.chunks.join('');
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
buffers = new Map();
|
||||
});
|
||||
|
||||
describe('appendToBuffer', () => {
|
||||
it('should create new buffer entry on first append', () => {
|
||||
const length = appendToBuffer('session-1', 'hello');
|
||||
expect(length).toBe(5);
|
||||
expect(buffers.has('session-1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should append to existing buffer', () => {
|
||||
appendToBuffer('session-1', 'hello');
|
||||
const length = appendToBuffer('session-1', ' world');
|
||||
expect(length).toBe(11); // 5 + 6
|
||||
});
|
||||
|
||||
it('should track total length correctly across multiple appends', () => {
|
||||
appendToBuffer('session-1', 'a'); // 1
|
||||
appendToBuffer('session-1', 'bb'); // 3
|
||||
appendToBuffer('session-1', 'ccc'); // 6
|
||||
const length = appendToBuffer('session-1', 'dddd'); // 10
|
||||
expect(length).toBe(10);
|
||||
});
|
||||
|
||||
it('should maintain separate buffers for different sessions', () => {
|
||||
appendToBuffer('session-1', 'hello');
|
||||
appendToBuffer('session-2', 'world');
|
||||
|
||||
expect(buffers.get('session-1')?.totalLength).toBe(5);
|
||||
expect(buffers.get('session-2')?.totalLength).toBe(5);
|
||||
});
|
||||
|
||||
it('should handle empty strings', () => {
|
||||
appendToBuffer('session-1', 'hello');
|
||||
const length = appendToBuffer('session-1', '');
|
||||
expect(length).toBe(5);
|
||||
});
|
||||
|
||||
it('should handle large data chunks', () => {
|
||||
const largeData = 'x'.repeat(100000);
|
||||
const length = appendToBuffer('session-1', largeData);
|
||||
expect(length).toBe(100000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBufferedOutput', () => {
|
||||
it('should return undefined for non-existent session', () => {
|
||||
const output = getBufferedOutput('non-existent');
|
||||
expect(output).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for empty buffer', () => {
|
||||
buffers.set('session-1', { chunks: [], totalLength: 0 });
|
||||
const output = getBufferedOutput('session-1');
|
||||
expect(output).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should join chunks correctly', () => {
|
||||
appendToBuffer('session-1', 'hello');
|
||||
appendToBuffer('session-1', ' ');
|
||||
appendToBuffer('session-1', 'world');
|
||||
|
||||
const output = getBufferedOutput('session-1');
|
||||
expect(output).toBe('hello world');
|
||||
});
|
||||
|
||||
it('should preserve order of chunks', () => {
|
||||
appendToBuffer('session-1', '1');
|
||||
appendToBuffer('session-1', '2');
|
||||
appendToBuffer('session-1', '3');
|
||||
|
||||
const output = getBufferedOutput('session-1');
|
||||
expect(output).toBe('123');
|
||||
});
|
||||
|
||||
it('should handle special characters', () => {
|
||||
appendToBuffer('session-1', '{"type": "test"}');
|
||||
appendToBuffer('session-1', '\n');
|
||||
appendToBuffer('session-1', '{"type": "test2"}');
|
||||
|
||||
const output = getBufferedOutput('session-1');
|
||||
expect(output).toBe('{"type": "test"}\n{"type": "test2"}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buffer cleanup', () => {
|
||||
it('should allow deletion of buffer entries', () => {
|
||||
appendToBuffer('session-1', 'data');
|
||||
buffers.delete('session-1');
|
||||
expect(buffers.has('session-1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not affect other buffers on deletion', () => {
|
||||
appendToBuffer('session-1', 'data1');
|
||||
appendToBuffer('session-2', 'data2');
|
||||
|
||||
buffers.delete('session-1');
|
||||
|
||||
expect(buffers.has('session-1')).toBe(false);
|
||||
expect(getBufferedOutput('session-2')).toBe('data2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance characteristics', () => {
|
||||
it('should maintain O(1) append by tracking totalLength incrementally', () => {
|
||||
// This test verifies the implementation doesn't use reduce()
|
||||
// by checking that totalLength matches sum of chunk lengths
|
||||
const chunks = ['chunk1', 'chunk2', 'chunk3', 'chunk4', 'chunk5'];
|
||||
|
||||
for (const chunk of chunks) {
|
||||
appendToBuffer('session-1', chunk);
|
||||
}
|
||||
|
||||
const buffer = buffers.get('session-1')!;
|
||||
const actualSum = buffer.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||
|
||||
expect(buffer.totalLength).toBe(actualSum);
|
||||
});
|
||||
|
||||
it('should handle many small appends efficiently', () => {
|
||||
// Simulate streaming output with many small chunks
|
||||
const startTime = Date.now();
|
||||
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
appendToBuffer('session-1', 'x');
|
||||
}
|
||||
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
// Should complete in under 100ms for 10000 appends
|
||||
// (generous threshold to avoid flaky tests)
|
||||
expect(elapsed).toBeLessThan(100);
|
||||
expect(buffers.get('session-1')?.totalLength).toBe(10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Debug Logging Tests
|
||||
// ============================================================================
|
||||
// These tests verify the conditional debug logging behavior.
|
||||
|
||||
describe('Debug Logging', () => {
|
||||
// Simulate the debugLog function
|
||||
function createDebugLog(isEnabled: boolean) {
|
||||
const logs: string[] = [];
|
||||
|
||||
function debugLog(prefix: string, message: string, ...args: unknown[]): void {
|
||||
if (isEnabled) {
|
||||
logs.push(`[${prefix}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { debugLog, logs };
|
||||
}
|
||||
|
||||
it('should log when debug is enabled', () => {
|
||||
const { debugLog, logs } = createDebugLog(true);
|
||||
|
||||
debugLog('GroupChat:Debug', 'Test message');
|
||||
|
||||
expect(logs).toHaveLength(1);
|
||||
expect(logs[0]).toBe('[GroupChat:Debug] Test message');
|
||||
});
|
||||
|
||||
it('should not log when debug is disabled', () => {
|
||||
const { debugLog, logs } = createDebugLog(false);
|
||||
|
||||
debugLog('GroupChat:Debug', 'Test message');
|
||||
|
||||
expect(logs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should format message with prefix correctly', () => {
|
||||
const { debugLog, logs } = createDebugLog(true);
|
||||
|
||||
debugLog('WebBroadcast', 'Broadcasting to session');
|
||||
|
||||
expect(logs[0]).toBe('[WebBroadcast] Broadcasting to session');
|
||||
});
|
||||
|
||||
it('should handle multiple log calls', () => {
|
||||
const { debugLog, logs } = createDebugLog(true);
|
||||
|
||||
debugLog('Test', 'Message 1');
|
||||
debugLog('Test', 'Message 2');
|
||||
debugLog('Test', 'Message 3');
|
||||
|
||||
expect(logs).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Session ID Parsing Tests
|
||||
// ============================================================================
|
||||
// These tests verify the parseParticipantSessionId logic.
|
||||
|
||||
describe('parseParticipantSessionId', () => {
|
||||
// Simulate the parsing function
|
||||
function parseParticipantSessionId(
|
||||
sessionId: string
|
||||
): { groupChatId: string; participantName: string } | null {
|
||||
if (!sessionId.includes('-participant-')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const REGEX_PARTICIPANT_UUID =
|
||||
/^group-chat-(.+)-participant-(.+)-([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i;
|
||||
const REGEX_PARTICIPANT_TIMESTAMP = /^group-chat-(.+)-participant-(.+)-(\d{13,})$/;
|
||||
const REGEX_PARTICIPANT_FALLBACK = /^group-chat-(.+)-participant-([^-]+)-/;
|
||||
|
||||
const uuidMatch = sessionId.match(REGEX_PARTICIPANT_UUID);
|
||||
if (uuidMatch) {
|
||||
return { groupChatId: uuidMatch[1], participantName: uuidMatch[2] };
|
||||
}
|
||||
|
||||
const timestampMatch = sessionId.match(REGEX_PARTICIPANT_TIMESTAMP);
|
||||
if (timestampMatch) {
|
||||
return { groupChatId: timestampMatch[1], participantName: timestampMatch[2] };
|
||||
}
|
||||
|
||||
const fallbackMatch = sessionId.match(REGEX_PARTICIPANT_FALLBACK);
|
||||
if (fallbackMatch) {
|
||||
return { groupChatId: fallbackMatch[1], participantName: fallbackMatch[2] };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
it('should return null for non-participant session IDs', () => {
|
||||
expect(parseParticipantSessionId('session-123')).toBeNull();
|
||||
expect(parseParticipantSessionId('group-chat-test-moderator-123')).toBeNull();
|
||||
});
|
||||
|
||||
it('should parse UUID-based participant session IDs', () => {
|
||||
const result = parseParticipantSessionId(
|
||||
'group-chat-my-chat-participant-Claude-a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
||||
);
|
||||
expect(result).toEqual({
|
||||
groupChatId: 'my-chat',
|
||||
participantName: 'Claude',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse timestamp-based participant session IDs', () => {
|
||||
const result = parseParticipantSessionId('group-chat-my-chat-participant-Claude-1705678901234');
|
||||
expect(result).toEqual({
|
||||
groupChatId: 'my-chat',
|
||||
participantName: 'Claude',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle participant names with hyphens using UUID format', () => {
|
||||
const result = parseParticipantSessionId(
|
||||
'group-chat-my-chat-participant-Claude-Code-a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
||||
);
|
||||
expect(result).toEqual({
|
||||
groupChatId: 'my-chat',
|
||||
participantName: 'Claude-Code',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle group chat IDs with hyphens', () => {
|
||||
const result = parseParticipantSessionId(
|
||||
'group-chat-my-complex-chat-id-participant-Agent-1705678901234'
|
||||
);
|
||||
expect(result).toEqual({
|
||||
groupChatId: 'my-complex-chat-id',
|
||||
participantName: 'Agent',
|
||||
});
|
||||
});
|
||||
|
||||
it('should prefer UUID match over timestamp match', () => {
|
||||
// This session ID could theoretically match both patterns
|
||||
// but UUID should be tried first
|
||||
const result = parseParticipantSessionId(
|
||||
'group-chat-chat-participant-Agent-a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
||||
);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.participantName).toBe('Agent');
|
||||
});
|
||||
});
|
||||
|
||||
// Need to import beforeEach for buffer tests
|
||||
import { beforeEach } from 'vitest';
|
||||
@@ -98,7 +98,7 @@ const DEBUG_GROUP_CHAT =
|
||||
process.env.NODE_ENV === 'development' || process.env.DEBUG_GROUP_CHAT === '1';
|
||||
|
||||
/** Log debug message only in development mode. Avoids overhead in production. */
|
||||
|
||||
|
||||
function debugLog(prefix: string, message: string, ...args: any[]): void {
|
||||
if (DEBUG_GROUP_CHAT) {
|
||||
console.log(`[${prefix}] ${message}`, ...args);
|
||||
@@ -2812,25 +2812,26 @@ function setupIpcHandlers() {
|
||||
// Buffer for group chat output (keyed by sessionId)
|
||||
// We buffer output and only route it on process exit to avoid duplicate messages from streaming chunks
|
||||
// Uses array of chunks for O(1) append performance instead of O(n) string concatenation
|
||||
const groupChatOutputBuffers = new Map<string, string[]>();
|
||||
// Tracks totalLength incrementally to avoid O(n) reduce on every append
|
||||
const groupChatOutputBuffers = new Map<string, { chunks: string[]; totalLength: number }>();
|
||||
|
||||
/** Append data to group chat output buffer. O(1) operation. */
|
||||
function appendToGroupChatBuffer(sessionId: string, data: string): number {
|
||||
let chunks = groupChatOutputBuffers.get(sessionId);
|
||||
if (!chunks) {
|
||||
chunks = [];
|
||||
groupChatOutputBuffers.set(sessionId, chunks);
|
||||
let buffer = groupChatOutputBuffers.get(sessionId);
|
||||
if (!buffer) {
|
||||
buffer = { chunks: [], totalLength: 0 };
|
||||
groupChatOutputBuffers.set(sessionId, buffer);
|
||||
}
|
||||
chunks.push(data);
|
||||
// Return approximate total length for debug logging
|
||||
return chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||
buffer.chunks.push(data);
|
||||
buffer.totalLength += data.length;
|
||||
return buffer.totalLength;
|
||||
}
|
||||
|
||||
/** Get buffered output as a single string. Joins chunks on read. */
|
||||
function getGroupChatBufferedOutput(sessionId: string): string | undefined {
|
||||
const chunks = groupChatOutputBuffers.get(sessionId);
|
||||
if (!chunks || chunks.length === 0) return undefined;
|
||||
return chunks.join('');
|
||||
const buffer = groupChatOutputBuffers.get(sessionId);
|
||||
if (!buffer || buffer.chunks.length === 0) return undefined;
|
||||
return buffer.chunks.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user