| 110 | // superseded process so they can't corrupt a freshly-spawned shell's |
| 111 | // buffer (same race as the exit-handler guard above). |
| 112 | const onChunk = (chunk) => { |
| 113 | if (!isCurrent()) return; |
| 114 | this.buffer += chunk.toString('utf8'); |
| 115 | // Hard cap to prevent runaway commands from OOMing us. |
| 116 | // Be careful not to slice mid-sentinel, otherwise the head command |
| 117 | // never completes. Trim from the start, keep the tail intact. |
| 118 | if (this.buffer.length > this.maxOutputBytes * 4) { |
| 119 | // Look for the head's sentinel — if present, keep everything from |
| 120 | // before it minus the excess. Otherwise just keep the tail. |
| 121 | if (this.queue.length > 0) { |
| 122 | const head = this.queue[0]; |
| 123 | const idx = this.buffer.lastIndexOf(SENTINEL_PREFIX); |
| 124 | // If we have any sentinel prefix in the tail, slice up to it |
| 125 | if (idx > 0 && this.buffer.length - idx < 256) { |
| 126 | // Keep the tail starting from the most recent SENTINEL_PREFIX |
| 127 | const trimAmount = this.buffer.length - this.maxOutputBytes * 2; |
| 128 | if (trimAmount > 0 && trimAmount < idx) { |
| 129 | this.buffer = this.buffer.slice(trimAmount); |
| 130 | } |
| 131 | } else { |
| 132 | this.buffer = this.buffer.slice(-this.maxOutputBytes * 2); |
| 133 | } |
| 134 | } else { |
| 135 | this.buffer = this.buffer.slice(-this.maxOutputBytes * 2); |
| 136 | } |
| 137 | } |
| 138 | this._drain(); |
| 139 | }; |
| 140 | proc.stdout.on('data', onChunk); |
| 141 | proc.stderr.on('data', onChunk); |
| 142 | |