diff --git a/src/web/public/app.js b/src/web/public/app.js index 558a5333..6b628eda 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -624,8 +624,8 @@ class CodemanApp { // oversized terminal.write() calls that triggered the stalls. // Disable with ?nowebgl URL param if GPU issues return. this._webglAddon = null; - const isMobile = MobileDetection.getDeviceType() === 'mobile'; - if (!isMobile && !new URLSearchParams(location.search).has('nowebgl') && typeof WebglAddon !== 'undefined') { + const skipWebGL = MobileDetection.getDeviceType() !== 'desktop'; + if (!skipWebGL && !new URLSearchParams(location.search).has('nowebgl') && typeof WebglAddon !== 'undefined') { try { this._webglAddon = new WebglAddon.WebglAddon(); this._webglAddon.onContextLoss(() => { @@ -10157,7 +10157,6 @@ class CodemanApp { windowEntry._lazyTerminal = true; windowEntry._lazyPaneTarget = paneData.paneTarget; windowEntry._lazySessionId = paneData.sessionId; - windowEntry._lazyBuffer = ''; } } else { this.initTeammateTerminal(windowId, paneData, win); diff --git a/src/web/public/subagent-windows.js b/src/web/public/subagent-windows.js index dec4fc55..8cf453b0 100644 --- a/src/web/public/subagent-windows.js +++ b/src/web/public/subagent-windows.js @@ -471,13 +471,9 @@ Object.assign(CodemanApp.prototype, { // restore → _restoreTeammateTerminalFromLazy() → re-creates Terminal // create (hidden/minimized) → skip initTeammateTerminal, set _lazyTerminal // - // The pane buffer is always re-fetched from the API on restore, so no - // client-side buffer accumulation is needed (the tmux pane is the source - // of truth). Regular (non-teammate) subagent windows use activity HTML - // and are unaffected by this optimization. - - /** Max bytes to buffer for a minimized teammate terminal (256KB). */ - _LAZY_TERMINAL_BUFFER_CAP: 256 * 1024, + // The tmux pane buffer is re-fetched from the API on restore. Regular + // (non-teammate) subagent windows use activity HTML and are unaffected + // by this optimization. /** * Dispose a teammate terminal when its window is minimized. @@ -495,8 +491,6 @@ Object.assign(CodemanApp.prototype, { windowData._lazyTerminal = true; windowData._lazyPaneTarget = termData.paneTarget; windowData._lazySessionId = termData.sessionId; - // Buffer for any data that arrives while minimized (from pendingData or future writes) - windowData._lazyBuffer = ''; } // Dispose the resize observer @@ -532,7 +526,6 @@ Object.assign(CodemanApp.prototype, { windowData._lazyTerminal = false; windowData._lazyPaneTarget = null; windowData._lazySessionId = null; - windowData._lazyBuffer = null; if (!paneTarget || !sessionId) return; @@ -541,29 +534,6 @@ Object.assign(CodemanApp.prototype, { this.initTeammateTerminal(agentId, paneInfo, windowData.element); }, - /** - * Append terminal data to a minimized teammate terminal's lazy buffer. - * Called when SSE data arrives for a minimized window. Caps at _LAZY_TERMINAL_BUFFER_CAP. - * Returns true if the data was buffered, false if the window is not in lazy mode. - */ - _bufferLazyTerminalData(agentId, data) { - const windowData = this.subagentWindows.get(agentId); - if (!windowData || !windowData._lazyTerminal) return false; - - if (windowData._lazyBuffer === null || windowData._lazyBuffer === undefined) { - windowData._lazyBuffer = ''; - } - - // Append data, capping total size - windowData._lazyBuffer += data; - if (windowData._lazyBuffer.length > this._LAZY_TERMINAL_BUFFER_CAP) { - // Keep only the tail to stay under cap - windowData._lazyBuffer = windowData._lazyBuffer.slice(-this._LAZY_TERMINAL_BUFFER_CAP); - } - - return true; - }, - // ═══════════════════════════════════════════════════════════════ // Subagent Floating Windows // ═══════════════════════════════════════════════════════════════ @@ -809,7 +779,6 @@ Object.assign(CodemanApp.prototype, { windowEntry._lazyTerminal = true; windowEntry._lazyPaneTarget = paneInfo.paneTarget; windowEntry._lazySessionId = paneInfo.sessionId; - windowEntry._lazyBuffer = ''; } } else { this.initTeammateTerminal(agentId, paneInfo, win); diff --git a/src/web/server.ts b/src/web/server.ts index ec8d9992..16da8825 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -601,7 +601,10 @@ export class WebServer extends EventEmitter { const query = req.query as { sessions?: string }; let sessionFilter: Set | null = null; if (query.sessions) { - const ids = query.sessions.split(',').filter(Boolean); + const ids = query.sessions + .split(',') + .map((s) => s.trim()) + .filter(Boolean); if (ids.length > 0) { sessionFilter = new Set(ids); } @@ -1963,7 +1966,8 @@ export class WebServer extends EventEmitter { // Client may have missed terminal data during backpressure. // Tell it to reload the active session's buffer to recover. try { - reply.raw.write(`event: ${SseEvent.SessionNeedsRefresh}\ndata: {}\n\n`); + const drainPadding = this._isTunnelActive ? SSE_PADDING : ''; + reply.raw.write(`event: ${SseEvent.SessionNeedsRefresh}\ndata: {}\n\n${drainPadding}`); } catch { /* client gone */ } @@ -1990,13 +1994,10 @@ export class WebServer extends EventEmitter { } // Performance optimization: serialize JSON once for all clients. // Only append Cloudflare tunnel padding for latency-sensitive events — - // high-frequency terminal data and recovery events need immediate proxy flush, - // but low-frequency metadata events (session:created, ralph:*, respawn:*, etc.) - // are small and infrequent enough that proxy buffering doesn't matter. - // Note: session:terminal bypasses broadcast() via flushSessionTerminalBatch(), - // but is included here for completeness in case the path changes. - const needsPadding = - this._isTunnelActive && (event === SseEvent.SessionTerminal || event === SseEvent.SessionNeedsRefresh); + // Recovery events need immediate proxy flush; low-frequency metadata events + // (session:created, ralph:*, respawn:*, etc.) don't need padding. + // Note: session:terminal has its own padding in flushSessionTerminalBatch(). + const needsPadding = this._isTunnelActive && event === SseEvent.SessionNeedsRefresh; const padding = needsPadding ? SSE_PADDING : ''; let message: string; try {