Checkpoint of shell optimization

fix(cli): Write shell command output to a file and limit memory buffered in UI

Fixes.

Checkpoint.

fix(core, cli): await outputStream.end() to prevent race conditions

This commit fixes a critical race condition where
was called synchronously without being awaited. This led to potential file
truncation or EBUSY errors on Windows when attempting to manipulate the file
immediately after the  call.

Additionally, this change removes fixed wait times (`setTimeout`) that
were previously used in test files as a band-aid.

fix(core): stream processed xterm output to file to remove spurious escape codes

test(core): update shell regression tests to use file_data events
This commit is contained in:
jacob314
2026-02-19 11:12:13 -08:00
committed by Spencer
parent 82e8d67a78
commit 954835123f
16 changed files with 963 additions and 63 deletions
@@ -661,4 +661,82 @@ describe('ToolOutputMaskingService', () => {
)['output'],
).toContain(MASKING_INDICATOR_TAG);
});
it('should use existing outputFile if available in the tool response', async () => {
// Setup: Create a large history to trigger masking
const largeContent = 'a'.repeat(60000);
const existingOutputFile = path.join(testTempDir, 'truly_full_output.txt');
await fs.promises.writeFile(existingOutputFile, 'truly full content');
const history: Content[] = [
{
role: 'user',
parts: [{ text: 'Old turn' }],
},
{
role: 'model',
parts: [
{
functionResponse: {
name: 'shell',
id: 'call-1',
response: {
output: largeContent,
outputFile: existingOutputFile,
},
},
},
],
},
// Protection buffer
{
role: 'user',
parts: [
{
functionResponse: {
name: 'padding',
response: { output: 'B'.repeat(60000) },
},
},
],
},
{
role: 'user',
parts: [{ text: 'Newest turn' }],
},
];
mockedEstimateTokenCountSync.mockImplementation((parts: Part[]) => {
const resp = parts[0].functionResponse?.response as Record<
string,
unknown
>;
const content = (resp?.['output'] as string) ?? JSON.stringify(resp);
if (content.includes(`<${MASKING_INDICATOR_TAG}`)) return 100;
const name = parts[0].functionResponse?.name;
if (name === 'shell') return 60000;
if (name === 'padding') return 60000;
return 10;
});
// Trigger masking
const result = await service.mask(history, mockConfig);
expect(result.maskedCount).toBe(2);
const maskedPart = result.newHistory[1].parts![0];
const maskedResponse = maskedPart.functionResponse?.response as Record<
string,
unknown
>;
const maskedOutput = maskedResponse['output'] as string;
// Verify the masked snippet points to the existing file
expect(maskedOutput).toContain(
`Full output available at: ${existingOutputFile}`,
);
// Verify the path in maskedOutput is exactly the one we provided
expect(maskedOutput).toContain(existingOutputFile);
});
});
@@ -182,25 +182,47 @@ export class ToolOutputMaskingService {
const toolName = part.functionResponse.name || 'unknown_tool';
const callId = part.functionResponse.id || Date.now().toString();
const safeToolName = sanitizeFilenamePart(toolName).toLowerCase();
const safeCallId = sanitizeFilenamePart(callId).toLowerCase();
const fileName = `${safeToolName}_${safeCallId}_${Math.random()
.toString(36)
.substring(7)}.txt`;
const filePath = path.join(toolOutputsDir, fileName);
await fsPromises.writeFile(filePath, content, 'utf-8');
const originalResponse =
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(part.functionResponse.response as Record<string, unknown>) || {};
const totalLines = content.split('\n').length;
const fileSizeMB = (
Buffer.byteLength(content, 'utf8') /
1024 /
1024
).toFixed(2);
let filePath = '';
let fileSizeMB = '0.00';
let totalLines = 0;
if (
typeof originalResponse['outputFile'] === 'string' &&
originalResponse['outputFile']
) {
filePath = originalResponse['outputFile'];
try {
const stats = await fsPromises.stat(filePath);
fileSizeMB = (stats.size / 1024 / 1024).toFixed(2);
// For truly full files, we don't count lines as it's too slow.
// We just indicate it's the full file.
totalLines = -1;
} catch {
// Fallback if file is gone
filePath = '';
}
}
if (!filePath) {
const safeToolName = sanitizeFilenamePart(toolName).toLowerCase();
const safeCallId = sanitizeFilenamePart(callId).toLowerCase();
const fileName = `${safeToolName}_${safeCallId}_${Math.random()
.toString(36)
.substring(7)}.txt`;
filePath = path.join(toolOutputsDir, fileName);
await fsPromises.writeFile(filePath, content, 'utf-8');
totalLines = content.split('\n').length;
fileSizeMB = (Buffer.byteLength(content, 'utf8') / 1024 / 1024).toFixed(
2,
);
}
let preview = '';
if (toolName === SHELL_TOOL_NAME) {