Skip to content

Commit fb3105f

Browse files
ENG-470/Chunking for large terminal outputs (RooCodeInc#2935)
* initial terminal output chunking * changeset * Update src/core/task/index.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * cleanup --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent bc87fdb commit fb3105f

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

.changeset/fast-readers-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
Added chunking to terminal outputs

src/core/task/index.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,34 +1137,85 @@ export class Task {
11371137

11381138
let userFeedback: { text?: string; images?: string[] } | undefined
11391139
let didContinue = false
1140-
const sendCommandOutput = async (line: string): Promise<void> => {
1140+
1141+
// Chunked terminal output buffering
1142+
const CHUNK_LINE_COUNT = 20
1143+
const CHUNK_BYTE_SIZE = 2048 // 2KB
1144+
const CHUNK_DEBOUNCE_MS = 100
1145+
1146+
let outputBuffer: string[] = []
1147+
let outputBufferSize: number = 0
1148+
let chunkTimer: NodeJS.Timeout | null = null
1149+
let chunkEnroute = false
1150+
1151+
const flushBuffer = async (force = false) => {
1152+
if (chunkEnroute || outputBuffer.length === 0) {
1153+
if (force && !chunkEnroute && outputBuffer.length > 0) {
1154+
// If force is true and no chunkEnroute, flush anyway
1155+
} else {
1156+
return
1157+
}
1158+
}
1159+
const chunk = outputBuffer.join("\n")
1160+
outputBuffer = []
1161+
outputBufferSize = 0
1162+
chunkEnroute = true
11411163
try {
1142-
const { response, text, images } = await this.ask("command_output", line)
1164+
const { response, text, images } = await this.ask("command_output", chunk)
11431165
if (response === "yesButtonClicked") {
11441166
// proceed while running
11451167
} else {
11461168
userFeedback = { text, images }
11471169
}
11481170
didContinue = true
1149-
process.continue() // continue past the await
1171+
process.continue()
11501172
} catch {
1151-
// This can only happen if this ask promise was ignored, so ignore this error
1173+
// ask promise was ignored
1174+
} finally {
1175+
chunkEnroute = false
1176+
// If more output accumulated while chunkEnroute, flush again
1177+
if (outputBuffer.length > 0) {
1178+
await flushBuffer()
1179+
}
11521180
}
11531181
}
11541182

1183+
const scheduleFlush = () => {
1184+
if (chunkTimer) {
1185+
clearTimeout(chunkTimer)
1186+
}
1187+
chunkTimer = setTimeout(() => flushBuffer(), CHUNK_DEBOUNCE_MS)
1188+
}
1189+
11551190
let result = ""
11561191
process.on("line", (line) => {
11571192
result += line + "\n"
1193+
11581194
if (!didContinue) {
1159-
sendCommandOutput(line)
1195+
outputBuffer.push(line)
1196+
outputBufferSize += Buffer.byteLength(line, "utf8")
1197+
// Flush if buffer is large enough
1198+
if (outputBuffer.length >= CHUNK_LINE_COUNT || outputBufferSize >= CHUNK_BYTE_SIZE) {
1199+
flushBuffer()
1200+
} else {
1201+
scheduleFlush()
1202+
}
11601203
} else {
11611204
this.say("command_output", line)
11621205
}
11631206
})
11641207

11651208
let completed = false
1166-
process.once("completed", () => {
1209+
process.once("completed", async () => {
11671210
completed = true
1211+
// Flush any remaining buffered output
1212+
if (!didContinue && outputBuffer.length > 0) {
1213+
if (chunkTimer) {
1214+
clearTimeout(chunkTimer)
1215+
chunkTimer = null
1216+
}
1217+
await flushBuffer(true)
1218+
}
11681219
})
11691220

11701221
process.once("no_shell_integration", async () => {

0 commit comments

Comments
 (0)