Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit b0ec581

Browse files
committed
fix: ensure lossless artifact storage and strict mode compatibility
- OutputInterceptor: Buffer ALL chunks before spilling to disk to preserve full content losslessly. Previously, the rolling tail buffer could drop middle content before the spill decision was made. - read_command_output schema: Include all properties in 'required' array for OpenAI strict mode compliance. With strict: true, all properties must be listed in required (optional ones use null union types).
1 parent d4680cd commit b0ec581

2 files changed

Lines changed: 23 additions & 18 deletions

File tree

src/core/prompts/tools/native-tools/read_command_output.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ export default {
7070
description: LIMIT_DESCRIPTION,
7171
},
7272
},
73-
required: ["artifact_id"],
73+
// With strict: true, ALL properties must be listed in required.
74+
// Optional params use union type with null (e.g., ["string", "null"]).
75+
required: ["artifact_id", "search", "offset", "limit"],
7476
additionalProperties: false,
7577
},
7678
},

src/integrations/terminal/OutputInterceptor.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export class OutputInterceptor {
6767
/** Number of bytes omitted from the middle */
6868
private omittedBytes: number = 0
6969

70+
/**
71+
* Pending chunks accumulated before spilling to disk.
72+
* These contain ALL content (lossless) until we decide to spill.
73+
* Once spilled, this array is cleared and subsequent writes go directly to disk.
74+
*/
75+
private pendingChunks: string[] = []
76+
7077
private writeStream: fs.WriteStream | null = null
7178
private artifactPath: string
7279
private totalBytes: number = 0
@@ -115,8 +122,11 @@ export class OutputInterceptor {
115122

116123
// Handle disk spilling for full output preservation
117124
if (!this.spilledToDisk) {
125+
// Accumulate ALL chunks for lossless disk storage
126+
this.pendingChunks.push(chunk)
127+
118128
if (this.totalBytes > this.previewBytes) {
119-
this.spillToDisk(chunk)
129+
this.spillToDisk()
120130
}
121131
} else {
122132
// Already spilling - write directly to disk
@@ -254,30 +264,23 @@ export class OutputInterceptor {
254264
*
255265
* @private
256266
*/
257-
private spillToDisk(currentChunk: string): void {
267+
private spillToDisk(): void {
258268
// Ensure directory exists
259269
const dir = path.dirname(this.artifactPath)
260270
if (!fs.existsSync(dir)) {
261271
fs.mkdirSync(dir, { recursive: true })
262272
}
263273

264274
this.writeStream = fs.createWriteStream(this.artifactPath)
265-
// Write the full head buffer + any tail content accumulated so far
266-
// Note: We need to reconstruct full output seen so far
267-
// The full content before this chunk is: totalBytes - currentChunkBytes
268-
// But we've already been tracking head/tail, so we write head + omitted + tail + current
269-
// Actually, we need to write the complete original content
270-
// Since we're spilling on the chunk that pushes us over, we need to write everything
271-
// that came before plus this chunk
272-
273-
// Reconstruct: we have headBuffer (complete head) + whatever was in tail before trimming
274-
// For simplicity, write head + tail + current chunk (the tail already has some data)
275-
this.writeStream.write(this.headBuffer)
276-
if (this.tailBuffer.length > 0) {
277-
this.writeStream.write(this.tailBuffer)
275+
276+
// Write ALL pending chunks to disk for lossless storage.
277+
// This ensures no content is lost, even if the preview buffers have dropped middle content.
278+
for (const chunk of this.pendingChunks) {
279+
this.writeStream.write(chunk)
278280
}
279-
// Don't write currentChunk here - it was already processed into head/tail buffers
280-
// and will be written via the streaming path
281+
282+
// Clear pending chunks to free memory - subsequent writes go directly to disk
283+
this.pendingChunks = []
281284

282285
this.spilledToDisk = true
283286
}

0 commit comments

Comments
 (0)