Skip to content

Commit 76640b1

Browse files
Apply PR #20039: feat: bash->shell tool + pwsh/powershell/cmd/bash specific tool definitions so agents work better
2 parents 7e42956 + c16a0e0 commit 76640b1

34 files changed

Lines changed: 710 additions & 217 deletions

packages/app/src/pages/session/composer/session-permission-dock.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ export function SessionPermissionDock(props: {
1414

1515
const toolDescription = () => {
1616
const key = `settings.permissions.tool.${props.request.permission}.description`
17+
const fallback = props.request.permission === "shell" ? "settings.permissions.tool.bash.description" : key
1718
const value = language.t(key as Parameters<typeof language.t>[0])
18-
if (value === key) return ""
19+
if (value === key) return fallback === key ? "" : language.t(fallback as Parameters<typeof language.t>[0])
1920
return value
2021
}
2122

packages/opencode/src/acp/agent.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { LoadAPIKeyError } from "ai"
5151
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
5252
import { applyPatch } from "diff"
5353
import { InstallationVersion } from "@opencode-ai/core/installation/version"
54+
import { ShellToolID } from "@/tool/shell/id"
5455

5556
type ModeOption = { id: string; name: string; description?: string }
5657
type ModelOption = { modelId: string; name: string }
@@ -144,7 +145,7 @@ export class Agent implements ACPAgent {
144145
private sessionManager: ACPSessionManager
145146
private eventAbort = new AbortController()
146147
private eventStarted = false
147-
private bashSnapshots = new Map<string, string>()
148+
private shellSnapshots = new Map<string, string>()
148149
private toolStarts = new Set<string>()
149150
private permissionQueues = new Map<string, Promise<void>>()
150151
private permissionOptions: PermissionOption[] = [
@@ -283,16 +284,16 @@ export class Agent implements ACPAgent {
283284

284285
switch (part.state.status) {
285286
case "pending":
286-
this.bashSnapshots.delete(part.callID)
287+
this.shellSnapshots.delete(part.callID)
287288
return
288289

289290
case "running":
290-
const output = this.bashOutput(part)
291+
const output = this.shellOutput(part)
291292
const content: ToolCallContent[] = []
292293
if (output) {
293294
const hash = Hash.fast(output)
294-
if (part.tool === "bash") {
295-
if (this.bashSnapshots.get(part.callID) === hash) {
295+
if (part.tool === ShellToolID.id) {
296+
if (this.shellSnapshots.get(part.callID) === hash) {
296297
await this.connection
297298
.sessionUpdate({
298299
sessionId,
@@ -311,7 +312,7 @@ export class Agent implements ACPAgent {
311312
})
312313
return
313314
}
314-
this.bashSnapshots.set(part.callID, hash)
315+
this.shellSnapshots.set(part.callID, hash)
315316
}
316317
content.push({
317318
type: "content",
@@ -342,7 +343,7 @@ export class Agent implements ACPAgent {
342343

343344
case "completed": {
344345
this.toolStarts.delete(part.callID)
345-
this.bashSnapshots.delete(part.callID)
346+
this.shellSnapshots.delete(part.callID)
346347
const kind = toToolKind(part.tool)
347348
const content: ToolCallContent[] = [
348349
{
@@ -423,7 +424,7 @@ export class Agent implements ACPAgent {
423424
}
424425
case "error":
425426
this.toolStarts.delete(part.callID)
426-
this.bashSnapshots.delete(part.callID)
427+
this.shellSnapshots.delete(part.callID)
427428
await this.connection
428429
.sessionUpdate({
429430
sessionId,
@@ -837,10 +838,10 @@ export class Agent implements ACPAgent {
837838
await this.toolStart(sessionId, part)
838839
switch (part.state.status) {
839840
case "pending":
840-
this.bashSnapshots.delete(part.callID)
841+
this.shellSnapshots.delete(part.callID)
841842
break
842843
case "running":
843-
const output = this.bashOutput(part)
844+
const output = this.shellOutput(part)
844845
const runningContent: ToolCallContent[] = []
845846
if (output) {
846847
runningContent.push({
@@ -871,7 +872,7 @@ export class Agent implements ACPAgent {
871872
break
872873
case "completed":
873874
this.toolStarts.delete(part.callID)
874-
this.bashSnapshots.delete(part.callID)
875+
this.shellSnapshots.delete(part.callID)
875876
const kind = toToolKind(part.tool)
876877
const content: ToolCallContent[] = [
877878
{
@@ -951,7 +952,7 @@ export class Agent implements ACPAgent {
951952
break
952953
case "error":
953954
this.toolStarts.delete(part.callID)
954-
this.bashSnapshots.delete(part.callID)
955+
this.shellSnapshots.delete(part.callID)
955956
await this.connection
956957
.sessionUpdate({
957958
sessionId,
@@ -1105,8 +1106,8 @@ export class Agent implements ACPAgent {
11051106
}
11061107
}
11071108

1108-
private bashOutput(part: ToolPart) {
1109-
if (part.tool !== "bash") return
1109+
private shellOutput(part: ToolPart) {
1110+
if (part.tool !== ShellToolID.id) return
11101111
if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return
11111112
const output = part.state.metadata["output"]
11121113
if (typeof output !== "string") return
@@ -1549,9 +1550,9 @@ export class Agent implements ACPAgent {
15491550

15501551
function toToolKind(toolName: string): ToolKind {
15511552
const tool = toolName.toLocaleLowerCase()
1553+
if (tool === ShellToolID.id) return "execute"
1554+
15521555
switch (tool) {
1553-
case "bash":
1554-
return "execute"
15551556
case "webfetch":
15561557
return "fetch"
15571558

@@ -1576,6 +1577,8 @@ function toToolKind(toolName: string): ToolKind {
15761577

15771578
function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
15781579
const tool = toolName.toLocaleLowerCase()
1580+
if (tool === ShellToolID.id) return []
1581+
15791582
switch (tool) {
15801583
case "read":
15811584
case "edit":
@@ -1584,8 +1587,6 @@ function toLocations(toolName: string, input: Record<string, any>): { path: stri
15841587
case "glob":
15851588
case "grep":
15861589
return input["path"] ? [{ path: input["path"] }] : []
1587-
case "bash":
1588-
return []
15891590
default:
15901591
return []
15911592
}

packages/opencode/src/agent/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export const layer = Layer.effect(
166166
grep: "allow",
167167
glob: "allow",
168168
list: "allow",
169-
bash: "allow",
169+
shell: "allow",
170170
webfetch: "allow",
171171
websearch: "allow",
172172
codesearch: "allow",

packages/opencode/src/agent/prompt/explore.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ Guidelines:
99
- Use Glob for broad file pattern matching
1010
- Use Grep for searching file contents with regex
1111
- Use Read when you know the specific file path you need to read
12-
- Use Bash for file operations like copying, moving, or listing directory contents
12+
- Use Shell for file operations like copying, moving, or listing directory contents
1313
- Adapt your search approach based on the thoroughness level specified by the caller
1414
- Return file paths as absolute paths in your final response
1515
- For clear communication, avoid using emojis
16-
- Do not create any files, or run bash commands that modify the user's system state in any way
16+
- Do not create any files, or run shell commands that modify the user's system state in any way
1717

1818
Complete the user's search request efficiently and report your findings clearly.

packages/opencode/src/agent/prompt/title.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Your output must be:
1414
<rules>
1515
- you MUST use the same language as the user message you are summarizing
1616
- Title must be grammatically correct and read naturally - no word salad
17-
- Never include tool names in the title (e.g. "read tool", "bash tool", "edit tool")
17+
- Never include tool names in the title (e.g. "read tool", "shell tool", "edit tool")
1818
- Focus on the main topic or question the user needs to retrieve
1919
- Vary your phrasing - avoid repetitive patterns like always starting with "Analyzing"
2020
- When a file is mentioned, focus on WHAT the user wants to do WITH the file, not just that they shared it

packages/opencode/src/cli/cmd/agent.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import matter from "gray-matter"
1212
import { Instance } from "../../project/instance"
1313
import { EOL } from "os"
1414
import type { Argv } from "yargs"
15-
1615
type AgentMode = "all" | "primary" | "subagent"
1716

1817
// Permission keys (not raw tool names). Multiple tools can map to a single

packages/opencode/src/cli/cmd/github.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ export const GithubRunCommand = cmd({
879879
function subscribeSessionEvents() {
880880
const TOOL: Record<string, [string, string]> = {
881881
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
882-
bash: ["Bash", UI.Style.TEXT_DANGER_BOLD],
882+
shell: ["Shell", UI.Style.TEXT_DANGER_BOLD],
883883
edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
884884
glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
885885
grep: ["Grep", UI.Style.TEXT_INFO_BOLD],

packages/opencode/src/cli/cmd/run.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import { CodeSearchTool } from "../../tool/codesearch"
2323
import { WebSearchTool } from "../../tool/websearch"
2424
import { TaskTool } from "../../tool/task"
2525
import { SkillTool } from "../../tool/skill"
26-
import { BashTool } from "../../tool/bash"
26+
import { ShellTool } from "../../tool/shell"
27+
import { ShellToolID } from "../../tool/shell/id"
2728
import { TodoWriteTool } from "../../tool/todo"
2829
import { Locale } from "@/util/locale"
2930
import { AppRuntime } from "@/effect/app-runtime"
@@ -183,7 +184,7 @@ function skill(info: ToolProps<typeof SkillTool>) {
183184
})
184185
}
185186

186-
function bash(info: ToolProps<typeof BashTool>) {
187+
function shell(info: ToolProps<typeof ShellTool>) {
187188
const output = info.part.state.status === "completed" ? info.part.state.output?.trim() : undefined
188189
block(
189190
{
@@ -413,7 +414,7 @@ export const RunCommand = cmd({
413414
async function execute(sdk: OpencodeClient) {
414415
function tool(part: ToolPart) {
415416
try {
416-
if (part.tool === "bash") return bash(props<typeof BashTool>(part))
417+
if (part.tool === ShellToolID.id) return shell(props<typeof ShellTool>(part))
417418
if (part.tool === "glob") return glob(props<typeof GlobTool>(part))
418419
if (part.tool === "grep") return grep(props<typeof GrepTool>(part))
419420
if (part.tool === "read") return read(props<typeof ReadTool>(part))

packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ const TIPS = [
9595
"Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input",
9696
"Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight})",
9797
"Add {highlight}.md{/highlight} files to {highlight}.opencode/agent/{/highlight} for specialized AI personas",
98-
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools",
99-
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions',
98+
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}shell{/highlight}, and {highlight}webfetch{/highlight} tools",
99+
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular shell permissions',
100100
'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands',
101101
'Configure {highlight}"git push": "ask"{/highlight} to require approval before pushing',
102102
"OpenCode auto-formats files using prettier, gofmt, ruff, and more",
@@ -130,7 +130,7 @@ const TIPS = [
130130
"Use {highlight}instructions{/highlight} in config to load additional rules files",
131131
"Set agent {highlight}temperature{/highlight} from 0.0 (focused) to 1.0 (creative)",
132132
"Configure {highlight}steps{/highlight} to limit agentic iterations per request",
133-
'Set {highlight}"tools": {"bash": false}{/highlight} to disable specific tools',
133+
'Set {highlight}"tools": {"shell": false}{/highlight} to disable specific tools',
134134
'Set {highlight}"mcp_*": false{/highlight} to disable all tools from an MCP server',
135135
"Override global tool settings per agent configuration",
136136
'Set {highlight}"share": "auto"{/highlight} to automatically share all sessions',

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ import { Locale } from "@/util/locale"
3737
import type { Tool } from "@/tool/tool"
3838
import type { ReadTool } from "@/tool/read"
3939
import type { WriteTool } from "@/tool/write"
40-
import { BashTool } from "@/tool/bash"
40+
import { ShellTool } from "@/tool/shell"
41+
import { ShellToolID } from "@/tool/shell/id"
4142
import type { GlobTool } from "@/tool/glob"
4243
import { TodoWriteTool } from "@/tool/todo"
4344
import type { GrepTool } from "@/tool/grep"
@@ -1556,8 +1557,8 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess
15561557
return (
15571558
<Show when={!shouldHide()}>
15581559
<Switch>
1559-
<Match when={props.part.tool === "bash"}>
1560-
<Bash {...toolprops} />
1560+
<Match when={props.part.tool === ShellToolID.id}>
1561+
<Shell {...toolprops} />
15611562
</Match>
15621563
<Match when={props.part.tool === "glob"}>
15631564
<Glob {...toolprops} />
@@ -1791,7 +1792,7 @@ function BlockTool(props: {
17911792
)
17921793
}
17931794

1794-
function Bash(props: ToolProps<typeof BashTool>) {
1795+
function Shell(props: ToolProps<typeof ShellTool>) {
17951796
const { theme } = useTheme()
17961797
const sync = useSync()
17971798
const isRunning = createMemo(() => props.part.state.status === "running")

0 commit comments

Comments
 (0)