refactor: add shell-specific guidance to each tool prompt

This commit is contained in:
LukeParkerDev 2026-03-30 20:18:50 +10:00
parent 3e26c3ae83
commit 51ebba2975
5 changed files with 58 additions and 23 deletions

View file

@ -1,7 +1,13 @@
import { createShellTool } from "./util"
export const BashTool = createShellTool(
"bash",
"Bash",
"use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`).",
)
export const BashTool = createShellTool({
id: "bash",
shellName: "Bash",
chaining:
"use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`).",
guidance: `# Bash shell notes
- This is a POSIX-compatible shell. Standard Unix conventions apply.
- Use double quotes for variable interpolation, single quotes for literal strings.
- Use \`$(...)\` for command substitution (not backticks).
- Redirect stderr with \`2>&1\` or \`2>/dev/null\`.`,
})

View file

@ -1,7 +1,17 @@
import { createShellTool } from "./util"
export const PowershellTool = createShellTool(
"powershell",
"Windows PowerShell",
"avoid '&&' in this shell because Windows PowerShell 5.1 does not support it. Use PowerShell conditionals such as `cmd1; if ($?) { cmd2 }`",
)
export const PowershellTool = createShellTool({
id: "powershell",
shellName: "Windows PowerShell 5.1",
chaining:
"avoid '&&' in this shell because Windows PowerShell 5.1 does not support it. Use PowerShell conditionals such as `cmd1; if ($?) { cmd2 }` when later commands must depend on earlier success.",
guidance: `# Windows PowerShell 5.1 shell notes
- This is Windows PowerShell 5.1 (legacy), NOT PowerShell 7+. It does NOT support \`&&\` or \`||\` pipeline chain operators.
- For conditional chaining use: \`cmd1; if ($?) { cmd2 }\`
- Use double quotes for interpolated strings (\`"Hello $name"\`), single quotes for verbatim strings.
- Cmdlets use Verb-Noun naming (e.g., \`Get-ChildItem\`, \`Set-Content\`). Common aliases like \`ls\`, \`cat\`, \`rm\` resolve to cmdlets with different behavior than Unix equivalents.
- Use \`$(...)\` for subexpressions. Use \`@(...)\` for array expressions.
- To call a native executable whose path contains spaces, use the call operator: \`& "path/to/exe" args\`.
- Escape special characters with backtick (\\\`) not backslash.
- Some modern PowerShell features (ternary operator, null-coalescing, etc.) are NOT available in 5.1.`,
})

View file

@ -1,7 +1,15 @@
import { createShellTool } from "./util"
export const PwshTool = createShellTool(
"pwsh",
"PowerShell Core",
"use a single PowerShell call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`).",
)
export const PwshTool = createShellTool({
id: "pwsh",
shellName: "PowerShell 7+",
chaining:
"use a single PowerShell call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`).",
guidance: `# PowerShell 7+ (pwsh) shell notes
- This is PowerShell 7+ (Core), a cross-platform shell. It supports pipeline chain operators (\`&&\` and \`||\`).
- Use double quotes for interpolated strings (\`"Hello $name"\`), single quotes for verbatim strings.
- Cmdlets use Verb-Noun naming (e.g., \`Get-ChildItem\`, \`Set-Content\`). Common aliases like \`ls\`, \`cat\`, \`rm\` are available but resolve to cmdlets.
- Use \`$(...)\` for subexpressions. Use \`@(...)\` for array expressions.
- To call a native executable whose path contains spaces, use the call operator: \`& "path/to/exe" args\`.
- Escape special characters with backtick (\\\`) not backslash.`,
})

View file

@ -6,6 +6,8 @@ All commands run in ${directory} by default. Use the \`workdir\` parameter if yo
IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
${guidance}
Before executing the command, please follow these steps:
1. Directory Verification:

View file

@ -80,13 +80,17 @@ export async function resolvePath(text: string, root: string, shell: string) {
return path.resolve(root, text)
}
export function formatShellDescription(template: string, opts: { name: string; shellName: string; chaining: string }) {
export function formatShellDescription(
template: string,
opts: { name: string; shellName: string; chaining: string; guidance: string },
) {
return template
.replaceAll("${directory}", Instance.directory)
.replaceAll("${os}", process.platform)
.replaceAll("${shell}", opts.name)
.replaceAll("${shellName}", opts.shellName)
.replaceAll("${chaining}", opts.chaining)
.replaceAll("${guidance}", opts.guidance)
.replaceAll("${maxLines}", String(Truncate.MAX_LINES))
.replaceAll("${maxBytes}", String(Truncate.MAX_BYTES))
}
@ -102,16 +106,21 @@ export type ShellType = "bash" | "pwsh" | "powershell"
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
export function createShellTool(id: ShellType, shellName: string, chaining: string) {
const log = Log.create({ service: `${id}-tool` })
export function createShellTool(opts: { id: ShellType; shellName: string; chaining: string; guidance: string }) {
const log = Log.create({ service: `${opts.id}-tool` })
return Tool.define(id, async () => {
return Tool.define(opts.id, async () => {
const shell = Shell.acceptable()
const name = Shell.name(shell)
log.info(`${id} tool using shell`, { shell, name })
log.info(`${opts.id} tool using shell`, { shell, name })
return {
description: formatShellDescription(DESCRIPTION, { name, shellName, chaining }),
description: formatShellDescription(DESCRIPTION, {
name,
shellName: opts.shellName,
chaining: opts.chaining,
guidance: opts.guidance,
}),
parameters: z.object({
command: z.string().describe("The command to execute"),
timeout: z.number().describe("Optional timeout in milliseconds").optional(),
@ -138,11 +147,11 @@ export function createShellTool(id: ShellType, shellName: string, chaining: stri
command: params.command,
cwd,
shell,
shellType: id,
shellType: opts.id,
})
if (!Instance.containsPath(cwd)) scan.dirs.add(cwd)
await askPermission(ctx, scan, id)
await askPermission(ctx, scan, opts.id)
return ShellRunner.run(
{