From 63d2265316dd7c76619d25a26a119ba7aaffa903 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 28 Jan 2026 04:38:43 +0100 Subject: [PATCH] refactor: migrate session.ts and respawn-controller.ts to shared utilities - Replace local BufferAccumulator in session.ts with shared utility - Replace local BufferAccumulator in respawn-controller.ts with shared utility - Import buffer constants from config/buffer-limits.ts - Reduces code duplication and centralizes buffer management Co-Authored-By: Claude Opus 4.5 --- src/respawn-controller.ts | 70 ++------------------------ src/session.ts | 102 ++++---------------------------------- 2 files changed, 14 insertions(+), 158 deletions(-) diff --git a/src/respawn-controller.ts b/src/respawn-controller.ts index a885be7b..7b4593c7 100644 --- a/src/respawn-controller.ts +++ b/src/respawn-controller.ts @@ -38,20 +38,11 @@ import { EventEmitter } from 'node:events'; import { Session } from './session.js'; import { AiIdleChecker, type AiCheckResult, type AiCheckState } from './ai-idle-checker.js'; import { AiPlanChecker, type AiPlanCheckResult } from './ai-plan-checker.js'; - -// ========== Configuration Constants ========== - -/** - * Maximum terminal buffer size for respawn controller. - * Buffer is trimmed when this limit is exceeded to prevent memory issues. - */ -const MAX_RESPAWN_BUFFER_SIZE = 1024 * 1024; // 1MB - -/** - * Size to trim buffer to when MAX_RESPAWN_BUFFER_SIZE is exceeded. - * Keeps the most recent output for pattern detection. - */ -const RESPAWN_BUFFER_TRIM_SIZE = 512 * 1024; // 512KB +import { BufferAccumulator } from './utils/buffer-accumulator.js'; +import { + MAX_RESPAWN_BUFFER_SIZE, + TRIM_RESPAWN_BUFFER_TO as RESPAWN_BUFFER_TRIM_SIZE, +} from './config/buffer-limits.js'; // ========== Constants ========== @@ -153,57 +144,6 @@ export interface DetectionStatus { nextAction: string; } -// ========== Buffer Accumulator ========== - -/** - * High-performance buffer accumulator using array-based collection. - * Reduces GC pressure by avoiding repeated string concatenation. - */ -class BufferAccumulator { - private chunks: string[] = []; - private totalLength: number = 0; - private readonly maxSize: number; - private readonly trimSize: number; - - constructor(maxSize: number, trimSize: number) { - this.maxSize = maxSize; - this.trimSize = trimSize; - } - - append(data: string): void { - if (!data) return; - this.chunks.push(data); - this.totalLength += data.length; - if (this.totalLength > this.maxSize) { - this.trim(); - } - } - - get value(): string { - if (this.chunks.length === 0) return ''; - if (this.chunks.length === 1) return this.chunks[0]; - const result = this.chunks.join(''); - this.chunks = [result]; - return result; - } - - get length(): number { - return this.totalLength; - } - - clear(): void { - this.chunks = []; - this.totalLength = 0; - } - - private trim(): void { - const full = this.chunks.join(''); - const trimmed = full.slice(-this.trimSize); - this.chunks = [trimmed]; - this.totalLength = trimmed.length; - } -} - // ========== Type Definitions ========== /** diff --git a/src/session.ts b/src/session.ts index 6a7c38b2..33bebf7a 100644 --- a/src/session.ts +++ b/src/session.ts @@ -26,33 +26,20 @@ import { TaskTracker, type BackgroundTask } from './task-tracker.js'; import { RalphTracker } from './ralph-tracker.js'; import { BashToolParser } from './bash-tool-parser.js'; import { ScreenManager } from './screen-manager.js'; +import { BufferAccumulator } from './utils/buffer-accumulator.js'; +import { + MAX_TERMINAL_BUFFER_SIZE, + TRIM_TERMINAL_TO as TERMINAL_BUFFER_TRIM_SIZE, + MAX_TEXT_OUTPUT_SIZE, + TRIM_TEXT_TO as TEXT_OUTPUT_TRIM_SIZE, + MAX_MESSAGES, + MAX_LINE_BUFFER_SIZE, +} from './config/buffer-limits.js'; export type { BackgroundTask } from './task-tracker.js'; export type { RalphTrackerState, RalphTodoItem, ActiveBashTool } from './types.js'; export { withTimeout }; -// ============================================================================ -// Buffer Size Constants -// ============================================================================ - -/** Maximum terminal buffer size in characters (2MB) - reduced from 5MB for better render performance */ -const MAX_TERMINAL_BUFFER_SIZE = 2 * 1024 * 1024; - -/** When trimming terminal buffer, keep the most recent portion (1.5MB) */ -const TERMINAL_BUFFER_TRIM_SIZE = 1.5 * 1024 * 1024; - -/** Maximum text output buffer size (1MB) - ANSI-stripped text */ -const MAX_TEXT_OUTPUT_SIZE = 1 * 1024 * 1024; - -/** When trimming text output, keep the most recent portion (768KB) */ -const TEXT_OUTPUT_TRIM_SIZE = 768 * 1024; - -/** Maximum number of Claude JSON messages to keep in memory */ -const MAX_MESSAGES = 1000; - -/** Maximum line buffer size (64KB) - prevents unbounded growth for long lines */ -const MAX_LINE_BUFFER_SIZE = 64 * 1024; - /** Line buffer flush interval (100ms) - forces processing of partial lines */ const LINE_BUFFER_FLUSH_INTERVAL = 100; @@ -155,77 +142,6 @@ export function getAugmentedPath(): string { return _augmentedPath; } -// ============================================================================ -// Buffer Accumulator (reduces GC pressure from string concatenation) -// ============================================================================ - -/** - * High-performance buffer accumulator using array-based collection. - * - * Reduces GC pressure by avoiding repeated string concatenation (`+=`). - * Instead, chunks are pushed to an array and joined only when needed. - * Automatically trims when size limits are exceeded. - */ -class BufferAccumulator { - private chunks: string[] = []; - private totalLength: number = 0; - private readonly maxSize: number; - private readonly trimSize: number; - - constructor(maxSize: number, trimSize: number) { - this.maxSize = maxSize; - this.trimSize = trimSize; - } - - /** Append data to the buffer */ - append(data: string): void { - if (!data) return; - this.chunks.push(data); - this.totalLength += data.length; - - // Trim if exceeded max size - if (this.totalLength > this.maxSize) { - this.trim(); - } - } - - /** Get the full buffer content (joins all chunks) */ - get value(): string { - if (this.chunks.length === 0) return ''; - if (this.chunks.length === 1) return this.chunks[0]; - - // Consolidate chunks on access - const result = this.chunks.join(''); - this.chunks = [result]; - return result; - } - - /** Get current buffer length without joining */ - get length(): number { - return this.totalLength; - } - - /** Clear the buffer */ - clear(): void { - this.chunks = []; - this.totalLength = 0; - } - - /** Set buffer to a specific value */ - set(value: string): void { - this.chunks = value ? [value] : []; - this.totalLength = value?.length || 0; - } - - /** Trim buffer to keep only the most recent data */ - private trim(): void { - const full = this.chunks.join(''); - const trimmed = full.slice(-this.trimSize); - this.chunks = [trimmed]; - this.totalLength = trimmed.length; - } -} - /** * Wraps a promise with a timeout to prevent indefinite hangs. * If the promise doesn't resolve within the timeout, rejects with TimeoutError.