fix(bash): honor explicit working directories
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import {
|
||||
createBashToolDefinition,
|
||||
type ExtensionAPI,
|
||||
type ExtensionContext,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import { type Static, Type } from "typebox";
|
||||
|
||||
const bashCwdParameters = Type.Object({
|
||||
command: Type.String({ description: "Shell command to execute" }),
|
||||
cwd: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
"Working directory. Relative paths resolve from the session working directory.",
|
||||
}),
|
||||
),
|
||||
timeout: Type.Optional(
|
||||
Type.Number({ description: "Timeout in seconds (optional, no default timeout)" }),
|
||||
),
|
||||
});
|
||||
|
||||
type BashCwdInput = Static<typeof bashCwdParameters>;
|
||||
type BuiltInBashDefinition = ReturnType<typeof createBashToolDefinition>;
|
||||
type BuiltInBashExecute = BuiltInBashDefinition["execute"];
|
||||
|
||||
async function resolveWorkingDirectory(
|
||||
sessionCwd: string,
|
||||
requestedCwd: string | undefined,
|
||||
): Promise<string> {
|
||||
const cwd = resolve(sessionCwd, requestedCwd ?? ".");
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await stat(cwd);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
throw new Error(
|
||||
`Working directory does not exist: ${cwd}\nCannot execute bash commands.`,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!metadata.isDirectory()) {
|
||||
throw new Error(
|
||||
`Working directory is not a directory: ${cwd}\nCannot execute bash commands.`,
|
||||
);
|
||||
}
|
||||
|
||||
return cwd;
|
||||
}
|
||||
|
||||
export function createContextualBashTool() {
|
||||
const baseTool = createBashToolDefinition(process.cwd());
|
||||
|
||||
return {
|
||||
...baseTool,
|
||||
description:
|
||||
`${baseTool.description} The optional cwd may be absolute or relative to the session working directory.`,
|
||||
parameters: bashCwdParameters,
|
||||
async execute(
|
||||
toolCallId: Parameters<BuiltInBashExecute>[0],
|
||||
params: BashCwdInput,
|
||||
signal: Parameters<BuiltInBashExecute>[2],
|
||||
onUpdate: Parameters<BuiltInBashExecute>[3],
|
||||
ctx: ExtensionContext,
|
||||
) {
|
||||
if (!ctx) {
|
||||
throw new Error("Bash execution context is unavailable");
|
||||
}
|
||||
|
||||
const cwd = await resolveWorkingDirectory(ctx.cwd, params.cwd);
|
||||
const contextualTool = createBashToolDefinition(cwd);
|
||||
return contextualTool.execute(
|
||||
toolCallId,
|
||||
{ command: params.command, timeout: params.timeout },
|
||||
signal,
|
||||
onUpdate,
|
||||
ctx,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function bashCwd(pi: ExtensionAPI): void {
|
||||
pi.registerTool(createContextualBashTool());
|
||||
}
|
||||
Reference in New Issue
Block a user