From 45d494a8d86cd2d47ac34abb7b15d25124b44e5a Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Thu, 4 Sep 2025 09:20:24 -0700 Subject: [PATCH] improve performance of shell commands with lots of output (#7680) --- .../core/src/services/shellExecutionService.test.ts | 2 +- packages/core/src/services/shellExecutionService.ts | 10 +++++----- packages/core/src/tools/shell.test.ts | 2 +- packages/core/src/tools/shell.ts | 7 +++++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/core/src/services/shellExecutionService.test.ts b/packages/core/src/services/shellExecutionService.test.ts index 9b02995ee6..3edce90268 100644 --- a/packages/core/src/services/shellExecutionService.test.ts +++ b/packages/core/src/services/shellExecutionService.test.ts @@ -133,7 +133,7 @@ describe('ShellExecutionService', () => { expect(onOutputEventMock).toHaveBeenCalledWith({ type: 'data', - chunk: 'file1.txt', + chunk: 'file1.txt\n', }); }); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index e42cc9bc48..f6f7fff7b6 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -348,7 +348,6 @@ export class ShellExecutionService { }); let processingChain = Promise.resolve(); let decoder: TextDecoder | null = null; - let output = ''; const outputChunks: Buffer[] = []; const error: Error | null = null; let exited = false; @@ -385,9 +384,10 @@ export class ShellExecutionService { if (isStreamingRawContent) { const decodedChunk = decoder.decode(data, { stream: true }); headlessTerminal.write(decodedChunk, () => { - const newStrippedOutput = getFullText(headlessTerminal); - output = newStrippedOutput; - onOutputEvent({ type: 'data', chunk: newStrippedOutput }); + onOutputEvent({ + type: 'data', + chunk: stripAnsi(decodedChunk), + }); resolve(); }); } else { @@ -420,7 +420,7 @@ export class ShellExecutionService { resolve({ rawOutput: finalBuffer, - output, + output: getFullText(headlessTerminal), exitCode, signal: signal ?? null, error, diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index e1874aaa42..88daa0cdfa 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -313,7 +313,7 @@ describe('ShellTool', () => { // Send a second chunk. THIS event triggers the update with the CUMULATIVE content. mockShellOutputCallback({ type: 'data', - chunk: 'hello world', + chunk: 'world', }); // It should have been called once now with the combined output. diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index f7275161fc..94e4bd85ec 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -132,6 +132,7 @@ class ShellToolInvocation extends BaseToolInvocation< ); let cumulativeOutput = ''; + let outputChunks: string[] = [cumulativeOutput]; let lastUpdateTime = Date.now(); let isBinaryStream = false; @@ -149,9 +150,11 @@ class ShellToolInvocation extends BaseToolInvocation< switch (event.type) { case 'data': if (isBinaryStream) break; - cumulativeOutput = event.chunk; - currentDisplayOutput = cumulativeOutput; + outputChunks.push(event.chunk); if (Date.now() - lastUpdateTime > OUTPUT_UPDATE_INTERVAL_MS) { + cumulativeOutput = outputChunks.join(''); + outputChunks = [cumulativeOutput]; + currentDisplayOutput = cumulativeOutput; shouldUpdate = true; } break;