fix(bash): preserve configured shell behavior
This commit is contained in:
@@ -4,15 +4,15 @@ Pi extension that adds an optional `cwd` argument to the built-in `bash` tool. A
|
||||
|
||||
The override delegates execution and rendering to Pi's built-in Bash implementation. Existing `command` and `timeout` calls remain compatible.
|
||||
|
||||
## Install
|
||||
## Try from this checkout
|
||||
|
||||
```bash
|
||||
pi install npm:@jbcode/pi-bash-cwd
|
||||
pi -e ./extensions/bash-cwd/index.ts
|
||||
```
|
||||
|
||||
Restart Pi or run `/reload` after installation. Pi warns that the extension replaces the built-in `bash` tool; that warning is expected.
|
||||
Pi warns that the extension replaces the built-in `bash` tool; that warning is expected. The package is not published yet. After publication, install it with `pi install npm:@jbcode/pi-bash-cwd` and restart Pi or run `/reload`.
|
||||
|
||||
Use this package with Pi 0.84.x. Remove it after moving to a Pi release that honors `ctx.cwd` and can be imported by extensions without undeclared runtime dependencies.
|
||||
Use this package with Pi 0.84.x. Remove it only after moving to a Pi release that exposes equivalent validated per-call `cwd` behavior and can be imported by extensions without undeclared runtime dependencies.
|
||||
|
||||
## Verify
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import {
|
||||
access,
|
||||
chmod,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
rm,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
@@ -29,7 +36,7 @@ async function executeIn(
|
||||
cwd?: string,
|
||||
) {
|
||||
const params = cwd === undefined ? { command } : { command, cwd };
|
||||
return createContextualBashTool().execute(
|
||||
return createContextualBashTool({}).execute(
|
||||
"bash-cwd-test",
|
||||
params,
|
||||
undefined,
|
||||
@@ -91,14 +98,55 @@ test("bash honors hidden-worktree and nested-package cwd arguments", async (t) =
|
||||
await assert.rejects(access(sentinel));
|
||||
});
|
||||
|
||||
test("bash preserves configured command prefixes and shell paths", async (t) => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "pi-bash-cwd-options-"));
|
||||
t.after(() => rm(directory, { recursive: true, force: true }));
|
||||
|
||||
const shellMarker = join(directory, "custom-shell-used");
|
||||
const shell = join(directory, "custom-shell");
|
||||
await writeFile(
|
||||
shell,
|
||||
`#!/bin/sh\nprintf used > "${shellMarker}"\nexec /bin/bash "$@"\n`,
|
||||
);
|
||||
await chmod(shell, 0o755);
|
||||
|
||||
await mkdir(join(directory, ".pi"));
|
||||
await writeFile(
|
||||
join(directory, ".pi", "settings.json"),
|
||||
JSON.stringify({
|
||||
shellCommandPrefix: "printf prefix-",
|
||||
shellPath: shell,
|
||||
}),
|
||||
);
|
||||
|
||||
const tool = createContextualBashTool();
|
||||
const result = await tool.execute(
|
||||
"bash-cwd-options-test",
|
||||
{ command: "printf command", cwd: directory },
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
cwd: directory,
|
||||
sessionManager: {
|
||||
getSessionId: () => "bash-cwd-test-session",
|
||||
getSessionFile: () => undefined,
|
||||
},
|
||||
isProjectTrusted: () => true,
|
||||
} as ExtensionContext,
|
||||
);
|
||||
|
||||
assert.equal(getOutput(result), "prefix-command");
|
||||
await access(shellMarker);
|
||||
});
|
||||
|
||||
test("bash exposes cwd without changing existing arguments", () => {
|
||||
const schema = createContextualBashTool().parameters;
|
||||
const schema = createContextualBashTool({}).parameters;
|
||||
assert.deepEqual(Object.keys(schema.properties), ["command", "cwd", "timeout"]);
|
||||
assert.deepEqual(schema.required, ["command"]);
|
||||
});
|
||||
|
||||
test("bash fails before execution when Pi omits the tool context", async () => {
|
||||
const tool = createContextualBashTool();
|
||||
const tool = createContextualBashTool({});
|
||||
await assert.rejects(
|
||||
tool.execute(
|
||||
"bash-cwd-test",
|
||||
|
||||
@@ -3,6 +3,9 @@ import { resolve } from "node:path";
|
||||
|
||||
import {
|
||||
createBashToolDefinition,
|
||||
getAgentDir,
|
||||
SettingsManager,
|
||||
type BashToolOptions,
|
||||
type ExtensionAPI,
|
||||
type ExtensionContext,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
@@ -52,8 +55,18 @@ async function resolveWorkingDirectory(
|
||||
return cwd;
|
||||
}
|
||||
|
||||
export function createContextualBashTool() {
|
||||
const baseTool = createBashToolDefinition(process.cwd());
|
||||
function loadBashOptions(ctx: ExtensionContext): BashToolOptions {
|
||||
const settings = SettingsManager.create(ctx.cwd, getAgentDir(), {
|
||||
projectTrusted: ctx.isProjectTrusted(),
|
||||
});
|
||||
return {
|
||||
commandPrefix: settings.getShellCommandPrefix(),
|
||||
shellPath: settings.getShellPath(),
|
||||
};
|
||||
}
|
||||
|
||||
export function createContextualBashTool(options?: BashToolOptions) {
|
||||
const baseTool = createBashToolDefinition(process.cwd(), options);
|
||||
|
||||
return {
|
||||
...baseTool,
|
||||
@@ -72,7 +85,10 @@ export function createContextualBashTool() {
|
||||
}
|
||||
|
||||
const cwd = await resolveWorkingDirectory(ctx.cwd, params.cwd);
|
||||
const contextualTool = createBashToolDefinition(cwd);
|
||||
const contextualTool = createBashToolDefinition(
|
||||
cwd,
|
||||
options ?? loadBashOptions(ctx),
|
||||
);
|
||||
return contextualTool.execute(
|
||||
toolCallId,
|
||||
{ command: params.command, timeout: params.timeout },
|
||||
|
||||
Reference in New Issue
Block a user