fix(coding-agent/exec): improved bash executor abort handling and resource cleanup
- Added abort event listener registration in bash executor to properly handle abort signals and clean up resources in finally block. - Improved bash tool error handling to distinguish between user-initiated aborts via AbortSignal and other cancellations, throwing ToolAbortError for aborted requests. - Wrapped bash executor command execution in try-finally block to ensure abort event listeners are properly removed after execution completes.
This commit is contained in:
@@ -73,50 +73,64 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
||||
shellSessions.set(sessionKey, shellSession);
|
||||
}
|
||||
|
||||
const result = await shellSession.run(
|
||||
{
|
||||
command: finalCommand,
|
||||
cwd: options?.cwd,
|
||||
env: options?.env,
|
||||
timeoutMs: options?.timeout,
|
||||
signal: options?.signal,
|
||||
},
|
||||
(err, chunk) => {
|
||||
if (!err) {
|
||||
enqueueChunk(chunk);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await pendingChunks;
|
||||
|
||||
// Handle timeout
|
||||
if (result.timedOut) {
|
||||
const annotation = options?.timeout
|
||||
? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
|
||||
: "Command timed out";
|
||||
return {
|
||||
exitCode: undefined,
|
||||
cancelled: true,
|
||||
...(await sink.dump(annotation)),
|
||||
};
|
||||
}
|
||||
|
||||
// Handle cancellation
|
||||
if (result.cancelled) {
|
||||
return {
|
||||
exitCode: undefined,
|
||||
cancelled: true,
|
||||
...(await sink.dump("Command cancelled")),
|
||||
};
|
||||
}
|
||||
|
||||
// Normal completion
|
||||
return {
|
||||
exitCode: result.exitCode,
|
||||
cancelled: false,
|
||||
...(await sink.dump()),
|
||||
const signal = options?.signal;
|
||||
const abortHandler = () => {
|
||||
shellSession.abort(signal?.reason instanceof Error ? signal.reason.message : undefined);
|
||||
};
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", abortHandler, { once: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await shellSession.run(
|
||||
{
|
||||
command: finalCommand,
|
||||
cwd: options?.cwd,
|
||||
env: options?.env,
|
||||
timeoutMs: options?.timeout,
|
||||
signal,
|
||||
},
|
||||
(err, chunk) => {
|
||||
if (!err) {
|
||||
enqueueChunk(chunk);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await pendingChunks;
|
||||
|
||||
// Handle timeout
|
||||
if (result.timedOut) {
|
||||
const annotation = options?.timeout
|
||||
? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
|
||||
: "Command timed out";
|
||||
return {
|
||||
exitCode: undefined,
|
||||
cancelled: true,
|
||||
...(await sink.dump(annotation)),
|
||||
};
|
||||
}
|
||||
|
||||
// Handle cancellation
|
||||
if (result.cancelled) {
|
||||
return {
|
||||
exitCode: undefined,
|
||||
cancelled: true,
|
||||
...(await sink.dump("Command cancelled")),
|
||||
};
|
||||
}
|
||||
|
||||
// Normal completion
|
||||
return {
|
||||
exitCode: result.exitCode,
|
||||
cancelled: false,
|
||||
...(await sink.dump()),
|
||||
};
|
||||
} finally {
|
||||
if (signal) {
|
||||
signal.removeEventListener("abort", abortHandler);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await pendingChunks;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user