Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions packages/core/src/codewhispererChat/tools/executeBash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ export interface CommandValidation {
warning?: string
}

// Interface for timestamped output chunks
interface TimestampedChunk {
timestamp: number
isStdout: boolean
content: string
isFirst: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be a field that actually needs a brief comment that gives insight into its purpose.

}

export class ExecuteBash {
private readonly command: string
private readonly workingDirectory?: string
Expand Down Expand Up @@ -207,22 +215,68 @@ export class ExecuteBash {
const stdoutBuffer: string[] = []
const stderrBuffer: string[] = []

let firstChunk = true
let firstStderrChunk = true
// Use a closure boolean value firstChunk and a function to get and set its value
let isFirstChunk = true
const getAndSetFirstChunk = (newValue: boolean): boolean => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function getAndSetFirstChunk can avoid duplicate code in line 260 and line 271.

const oldValue = isFirstChunk
isFirstChunk = newValue
return oldValue
}

// Use a queue to maintain chronological order of chunks
// This ensures that the output is processed in the exact order it was generated by the child process.
const outputQueue: TimestampedChunk[] = []
let processingQueue = false

// Process the queue in order
const processQueue = () => {
if (processingQueue || outputQueue.length === 0) {
return
}

processingQueue = true

try {
// Sort by timestamp to ensure chronological order
outputQueue.sort((a, b) => a.timestamp - b.timestamp)

while (outputQueue.length > 0) {
const chunk = outputQueue.shift()!
ExecuteBash.handleTimestampedChunk(chunk, stdoutBuffer, stderrBuffer, updates)
}
} finally {
processingQueue = false
}
}

const childProcessOptions: ChildProcessOptions = {
spawnOptions: {
cwd: this.workingDirectory,
stdio: ['pipe', 'pipe', 'pipe'],
},
collect: false,
waitForStreams: true,
onStdout: (chunk: string) => {
ExecuteBash.handleChunk(firstChunk ? '```console\n' + chunk : chunk, stdoutBuffer, updates)
firstChunk = false
onStdout: async (chunk: string) => {
const isFirst = getAndSetFirstChunk(false)
const timestamp = Date.now()
outputQueue.push({
timestamp,
isStdout: true,
content: chunk,
isFirst,
})
processQueue()
},
onStderr: (chunk: string) => {
ExecuteBash.handleChunk(firstStderrChunk ? '```console\n' + chunk : chunk, stderrBuffer, updates)
firstStderrChunk = false
onStderr: async (chunk: string) => {
const isFirst = getAndSetFirstChunk(false)
const timestamp = Date.now()
outputQueue.push({
timestamp,
isStdout: false,
content: chunk,
isFirst,
})
processQueue()
},
}

Expand Down Expand Up @@ -261,6 +315,17 @@ export class ExecuteBash {
})
}

private static handleTimestampedChunk(
chunk: TimestampedChunk,
stdoutBuffer: string[],
stderrBuffer: string[],
updates?: Writable
): void {
const buffer = chunk.isStdout ? stdoutBuffer : stderrBuffer
const content = chunk.isFirst ? '```console\n' + chunk.content : chunk.content
ExecuteBash.handleChunk(content, buffer, updates)
}

private static handleChunk(chunk: string, buffer: string[], updates?: Writable) {
try {
updates?.write(chunk)
Expand Down
Loading