feat(coding-agent): add bash spawn hook

This commit is contained in:
Mario Zechner 2026-02-01 23:17:51 +01:00
parent 25fa1fafde
commit 86b43c8eac
6 changed files with 81 additions and 3 deletions

View file

@ -47,6 +47,7 @@ export interface BashOperations {
onData: (data: Buffer) => void;
signal?: AbortSignal;
timeout?: number;
env?: NodeJS.ProcessEnv;
},
) => Promise<{ exitCode: number | null }>;
}
@ -55,7 +56,7 @@ export interface BashOperations {
* Default bash operations using local shell
*/
const defaultBashOperations: BashOperations = {
exec: (command, cwd, { onData, signal, timeout }) => {
exec: (command, cwd, { onData, signal, timeout, env }) => {
return new Promise((resolve, reject) => {
const { shell, args } = getShellConfig();
@ -67,7 +68,7 @@ const defaultBashOperations: BashOperations = {
const child = spawn(shell, [...args, command], {
cwd,
detached: true,
env: getShellEnv(),
env: env ?? getShellEnv(),
stdio: ["ignore", "pipe", "pipe"],
});
@ -135,16 +136,37 @@ const defaultBashOperations: BashOperations = {
},
};
export interface BashSpawnContext {
command: string;
cwd: string;
env: NodeJS.ProcessEnv;
}
export type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;
function resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {
const baseContext: BashSpawnContext = {
command,
cwd,
env: { ...getShellEnv() },
};
return spawnHook ? spawnHook(baseContext) : baseContext;
}
export interface BashToolOptions {
/** Custom operations for command execution. Default: local shell */
operations?: BashOperations;
/** Command prefix prepended to every command (e.g., "shopt -s expand_aliases" for alias support) */
commandPrefix?: string;
/** Hook to adjust command, cwd, or env before execution */
spawnHook?: BashSpawnHook;
}
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
const ops = options?.operations ?? defaultBashOperations;
const commandPrefix = options?.commandPrefix;
const spawnHook = options?.spawnHook;
return {
name: "bash",
@ -159,6 +181,7 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
) => {
// Apply command prefix if configured (e.g., "shopt -s expand_aliases" for alias support)
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
return new Promise((resolve, reject) => {
// We'll stream to a temp file if output gets large
@ -215,7 +238,12 @@ export function createBashTool(cwd: string, options?: BashToolOptions): AgentToo
}
};
ops.exec(resolvedCommand, cwd, { onData: handleData, signal, timeout })
ops.exec(spawnContext.command, spawnContext.cwd, {
onData: handleData,
signal,
timeout,
env: spawnContext.env,
})
.then(({ exitCode }) => {
// Close temp file stream
if (tempFileStream) {

View file

@ -1,5 +1,7 @@
export {
type BashOperations,
type BashSpawnContext,
type BashSpawnHook,
type BashToolDetails,
type BashToolInput,
type BashToolOptions,