Skip to content

Commit c3d087a

Browse files
authored
fix(chat): fix shell command execution output markdown block issue (aws#6995)
## Problem - fix shell command execution output markdown block issue - When the child process of bash command execution writes to both stdout and stderr, we need to main the output is processed in the exact order it was generated by the child process. ## Solution - Before this change ![image](https://github.com/user-attachments/assets/59190c23-4f0f-452b-a9bd-8efb25fd440d) - After this change ![image](https://github.com/user-attachments/assets/e8570eb8-477b-4611-8516-d62ce57b7373) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 2f15484 commit c3d087a

File tree

1 file changed

+73
-8
lines changed

1 file changed

+73
-8
lines changed

packages/core/src/codewhispererChat/tools/executeBash.ts

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ export interface CommandValidation {
117117
warning?: string
118118
}
119119

120+
// Interface for timestamped output chunks
121+
interface TimestampedChunk {
122+
timestamp: number
123+
isStdout: boolean
124+
content: string
125+
isFirst: boolean
126+
}
127+
120128
export class ExecuteBash {
121129
private readonly command: string
122130
private readonly workingDirectory?: string
@@ -207,22 +215,68 @@ export class ExecuteBash {
207215
const stdoutBuffer: string[] = []
208216
const stderrBuffer: string[] = []
209217

210-
let firstChunk = true
211-
let firstStderrChunk = true
218+
// Use a closure boolean value firstChunk and a function to get and set its value
219+
let isFirstChunk = true
220+
const getAndSetFirstChunk = (newValue: boolean): boolean => {
221+
const oldValue = isFirstChunk
222+
isFirstChunk = newValue
223+
return oldValue
224+
}
225+
226+
// Use a queue to maintain chronological order of chunks
227+
// This ensures that the output is processed in the exact order it was generated by the child process.
228+
const outputQueue: TimestampedChunk[] = []
229+
let processingQueue = false
230+
231+
// Process the queue in order
232+
const processQueue = () => {
233+
if (processingQueue || outputQueue.length === 0) {
234+
return
235+
}
236+
237+
processingQueue = true
238+
239+
try {
240+
// Sort by timestamp to ensure chronological order
241+
outputQueue.sort((a, b) => a.timestamp - b.timestamp)
242+
243+
while (outputQueue.length > 0) {
244+
const chunk = outputQueue.shift()!
245+
ExecuteBash.handleTimestampedChunk(chunk, stdoutBuffer, stderrBuffer, updates)
246+
}
247+
} finally {
248+
processingQueue = false
249+
}
250+
}
251+
212252
const childProcessOptions: ChildProcessOptions = {
213253
spawnOptions: {
214254
cwd: this.workingDirectory,
215255
stdio: ['pipe', 'pipe', 'pipe'],
216256
},
217257
collect: false,
218258
waitForStreams: true,
219-
onStdout: (chunk: string) => {
220-
ExecuteBash.handleChunk(firstChunk ? '```console\n' + chunk : chunk, stdoutBuffer, updates)
221-
firstChunk = false
259+
onStdout: async (chunk: string) => {
260+
const isFirst = getAndSetFirstChunk(false)
261+
const timestamp = Date.now()
262+
outputQueue.push({
263+
timestamp,
264+
isStdout: true,
265+
content: chunk,
266+
isFirst,
267+
})
268+
processQueue()
222269
},
223-
onStderr: (chunk: string) => {
224-
ExecuteBash.handleChunk(firstStderrChunk ? '```console\n' + chunk : chunk, stderrBuffer, updates)
225-
firstStderrChunk = false
270+
onStderr: async (chunk: string) => {
271+
const isFirst = getAndSetFirstChunk(false)
272+
const timestamp = Date.now()
273+
outputQueue.push({
274+
timestamp,
275+
isStdout: false,
276+
content: chunk,
277+
isFirst,
278+
})
279+
processQueue()
226280
},
227281
}
228282

@@ -261,6 +315,17 @@ export class ExecuteBash {
261315
})
262316
}
263317

318+
private static handleTimestampedChunk(
319+
chunk: TimestampedChunk,
320+
stdoutBuffer: string[],
321+
stderrBuffer: string[],
322+
updates?: Writable
323+
): void {
324+
const buffer = chunk.isStdout ? stdoutBuffer : stderrBuffer
325+
const content = chunk.isFirst ? '```console\n' + chunk.content : chunk.content
326+
ExecuteBash.handleChunk(content, buffer, updates)
327+
}
328+
264329
private static handleChunk(chunk: string, buffer: string[], updates?: Writable) {
265330
try {
266331
updates?.write(chunk)

0 commit comments

Comments
 (0)