Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
048ac63
refactor: split monolithic bash tool into separate bash/pwsh/powershe…
Hona Mar 30, 2026
67dfbcb
fix: use dynamic imports for tree-sitter and shell-aware metadata tags
Hona Mar 30, 2026
3e26c3a
refactor: extract shell tool factory to eliminate duplication
Hona Mar 30, 2026
51ebba2
refactor: add shell-specific guidance to each tool prompt
Hona Mar 30, 2026
48f9082
refactor: use positive tone in shell guidance prompts
Hona Mar 30, 2026
676519d
refactor: apply positive guidance and parameterize shell commands in …
Hona Mar 30, 2026
f21bf4a
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 3, 2026
95577c7
fix(config): preserve bash permission compatibility
Hona Apr 3, 2026
6ad6358
fix: render pwsh and powershell tools correctly in UI
Hona Apr 3, 2026
23e77fd
fix(shell): preserve powershell exit codes
Hona Apr 3, 2026
baf476f
test(shell): handle nullable exit metadata
Hona Apr 3, 2026
2eb9ae4
refactor(shell): centralize shell tool identity
Hona Apr 3, 2026
32ec366
fix(shell): keep shell config consistent
Hona Apr 3, 2026
2555117
fix(shell): avoid abort hangs and utf8 corruption
Hona Apr 3, 2026
39088e1
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 8, 2026
f1547de
ok
Hona Apr 8, 2026
ee0884a
fix(shell): preserve legacy bash compatibility
Hona Apr 8, 2026
f9a633b
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 23, 2026
b75f831
.
Hona Apr 23, 2026
3e30068
refactor: make shell the canonical tool internals
Hona Apr 23, 2026
6d66973
clean
Hona Apr 23, 2026
0d500a7
Create todo.spec.ts
Hona Apr 23, 2026
cffb8eb
.
Hona Apr 23, 2026
26d77ad
edges
Hona Apr 23, 2026
7266b48
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 23, 2026
4f8ff6a
.
Hona Apr 23, 2026
341b8e7
perms
Hona Apr 25, 2026
428b0c4
cmd
Hona Apr 25, 2026
f89955a
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 25, 2026
ecac4c4
split prompt/definition from logic
Hona Apr 25, 2026
790d181
slight accuracy
Hona Apr 25, 2026
2051cad
Update prompt.ts
Hona Apr 25, 2026
73ee7ae
Merge branch 'dev' into refactor-shells
Hona Apr 25, 2026
9dde86a
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 25, 2026
344dab3
Update next.test.ts
Hona Apr 25, 2026
5a7e69b
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 26, 2026
b1d9c57
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 27, 2026
ea277ba
css
Hona Apr 27, 2026
6ac33dd
test: update experimental api shell assertions
Hona Apr 27, 2026
9d9830b
no breaking changes
Hona Apr 28, 2026
70ca572
noooooooooo brekaing changes
Hona Apr 28, 2026
c16a0e0
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona Apr 28, 2026
d5ebfad
.
Hona Apr 28, 2026
f868719
.
Hona Apr 28, 2026
20c3461
f
Hona Apr 29, 2026
529a6ed
.
Hona Apr 29, 2026
9e0379f
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona May 2, 2026
ac5c1e0
.
Hona May 2, 2026
def5030
Merge remote-tracking branch 'upstream/dev' into refactor-shells
Hona May 2, 2026
7b5c333
javascript
Hona May 2, 2026
eb97cf5
lol
Hona May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions packages/opencode/src/acp/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { LoadAPIKeyError } from "ai"
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
import { applyPatch } from "diff"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { ShellID } from "@/tool/shell/id"

type ModeOption = { id: string; name: string; description?: string }
type ModelOption = { modelId: string; name: string }
Expand Down Expand Up @@ -144,7 +145,7 @@ export class Agent implements ACPAgent {
private sessionManager: ACPSessionManager
private eventAbort = new AbortController()
private eventStarted = false
private bashSnapshots = new Map<string, string>()
private shellSnapshots = new Map<string, string>()
private toolStarts = new Set<string>()
private permissionQueues = new Map<string, Promise<void>>()
private permissionOptions: PermissionOption[] = [
Expand Down Expand Up @@ -283,16 +284,16 @@ export class Agent implements ACPAgent {

switch (part.state.status) {
case "pending":
this.bashSnapshots.delete(part.callID)
this.shellSnapshots.delete(part.callID)
return

case "running":
const output = this.bashOutput(part)
const output = this.shellOutput(part)
const content: ToolCallContent[] = []
if (output) {
const hash = Hash.fast(output)
if (part.tool === "bash") {
if (this.bashSnapshots.get(part.callID) === hash) {
if (part.tool === ShellID.ToolID) {
if (this.shellSnapshots.get(part.callID) === hash) {
await this.connection
.sessionUpdate({
sessionId,
Expand All @@ -311,7 +312,7 @@ export class Agent implements ACPAgent {
})
return
}
this.bashSnapshots.set(part.callID, hash)
this.shellSnapshots.set(part.callID, hash)
}
content.push({
type: "content",
Expand Down Expand Up @@ -342,7 +343,7 @@ export class Agent implements ACPAgent {

case "completed": {
this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID)
this.shellSnapshots.delete(part.callID)
const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [
{
Expand Down Expand Up @@ -423,7 +424,7 @@ export class Agent implements ACPAgent {
}
case "error":
this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID)
this.shellSnapshots.delete(part.callID)
await this.connection
.sessionUpdate({
sessionId,
Expand Down Expand Up @@ -837,10 +838,10 @@ export class Agent implements ACPAgent {
await this.toolStart(sessionId, part)
switch (part.state.status) {
case "pending":
this.bashSnapshots.delete(part.callID)
this.shellSnapshots.delete(part.callID)
break
case "running":
const output = this.bashOutput(part)
const output = this.shellOutput(part)
const runningContent: ToolCallContent[] = []
if (output) {
runningContent.push({
Expand Down Expand Up @@ -871,7 +872,7 @@ export class Agent implements ACPAgent {
break
case "completed":
this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID)
this.shellSnapshots.delete(part.callID)
const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [
{
Expand Down Expand Up @@ -951,7 +952,7 @@ export class Agent implements ACPAgent {
break
case "error":
this.toolStarts.delete(part.callID)
this.bashSnapshots.delete(part.callID)
this.shellSnapshots.delete(part.callID)
await this.connection
.sessionUpdate({
sessionId,
Expand Down Expand Up @@ -1105,8 +1106,8 @@ export class Agent implements ACPAgent {
}
}

private bashOutput(part: ToolPart) {
if (part.tool !== "bash") return
private shellOutput(part: ToolPart) {
if (part.tool !== ShellID.ToolID) return
if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return
const output = part.state.metadata["output"]
if (typeof output !== "string") return
Expand Down Expand Up @@ -1549,9 +1550,11 @@ export class Agent implements ACPAgent {

function toToolKind(toolName: string): ToolKind {
const tool = toolName.toLocaleLowerCase()

switch (tool) {
case "bash":
case ShellID.ToolID:
return "execute"

case "webfetch":
return "fetch"

Expand All @@ -1576,6 +1579,7 @@ function toToolKind(toolName: string): ToolKind {

function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
const tool = toolName.toLocaleLowerCase()

switch (tool) {
case "read":
case "edit":
Expand All @@ -1584,7 +1588,7 @@ function toLocations(toolName: string, input: Record<string, any>): { path: stri
case "glob":
case "grep":
return input["path"] ? [{ path: input["path"] }] : []
case "bash":
case ShellID.ToolID:
return []
default:
return []
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,7 @@ export const GithubRunCommand = cmd({
function subscribeSessionEvents() {
const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
bash: ["Bash", UI.Style.TEXT_DANGER_BOLD],
bash: ["Shell", UI.Style.TEXT_DANGER_BOLD],
edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
Expand Down
7 changes: 4 additions & 3 deletions packages/opencode/src/cli/cmd/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import { WriteTool } from "../../tool/write"
import { WebSearchTool } from "../../tool/websearch"
import { TaskTool } from "../../tool/task"
import { SkillTool } from "../../tool/skill"
import { BashTool } from "../../tool/bash"
import { ShellTool } from "../../tool/shell"
import { ShellID } from "../../tool/shell/id"
import { TodoWriteTool } from "../../tool/todo"
import { Locale } from "@/util/locale"
import { AppRuntime } from "@/effect/app-runtime"
Expand Down Expand Up @@ -175,7 +176,7 @@ function skill(info: ToolProps<typeof SkillTool>) {
})
}

function bash(info: ToolProps<typeof BashTool>) {
function shell(info: ToolProps<typeof ShellTool>) {
const output = info.part.state.status === "completed" ? info.part.state.output?.trim() : undefined
block(
{
Expand Down Expand Up @@ -400,7 +401,7 @@ export const RunCommand = cmd({
async function execute(sdk: OpencodeClient) {
function tool(part: ToolPart) {
try {
if (part.tool === "bash") return bash(props<typeof BashTool>(part))
if (part.tool === ShellID.ToolID) return shell(props<typeof ShellTool>(part))
if (part.tool === "glob") return glob(props<typeof GlobTool>(part))
if (part.tool === "grep") return grep(props<typeof GrepTool>(part))
if (part.tool === "read") return read(props<typeof ReadTool>(part))
Expand Down
9 changes: 5 additions & 4 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import { Locale } from "@/util/locale"
import type { Tool } from "@/tool/tool"
import type { ReadTool } from "@/tool/read"
import type { WriteTool } from "@/tool/write"
import { BashTool } from "@/tool/bash"
import { ShellTool } from "@/tool/shell"
import { ShellID } from "@/tool/shell/id"
import type { GlobTool } from "@/tool/glob"
import { TodoWriteTool } from "@/tool/todo"
import type { GrepTool } from "@/tool/grep"
Expand Down Expand Up @@ -1552,8 +1553,8 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess
return (
<Show when={!shouldHide()}>
<Switch>
<Match when={props.part.tool === "bash"}>
<Bash {...toolprops} />
<Match when={props.part.tool === ShellID.ToolID}>
<Shell {...toolprops} />
</Match>
<Match when={props.part.tool === "glob"}>
<Glob {...toolprops} />
Expand Down Expand Up @@ -1784,7 +1785,7 @@ function BlockTool(props: {
)
}

function Bash(props: ToolProps<typeof BashTool>) {
function Shell(props: ToolProps<typeof ShellTool>) {
const { theme } = useTheme()
const sync = useSync()
const isRunning = createMemo(() => props.part.state.status === "running")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
import { Keybind } from "@/util/keybind"
import { Locale } from "@/util/locale"
import { Global } from "@opencode-ai/core/global"
import { ShellID } from "@/tool/shell/id"
import { useDialog } from "../../ui/dialog"
import { getScrollAcceleration } from "../../util/scroll"
import { useTuiConfig } from "../../context/tui-config"
Expand Down Expand Up @@ -287,7 +288,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
}
}

if (permission === "bash") {
if (permission === ShellID.ToolID) {
const title =
typeof data.description === "string" && data.description ? data.description : "Shell command"
const command = typeof data.command === "string" ? data.command : ""
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { Permission } from "@/permission"
import { SessionStatus } from "./status"
import { LLM } from "./llm"
import { Shell } from "@/shell/shell"
import { ShellID } from "@/tool/shell/id"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Truncate } from "@/tool/truncate"
import { decodeDataUrl } from "@/util/data-url"
Expand Down Expand Up @@ -789,7 +790,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
id: PartID.ascending(),
messageID: msg.id,
sessionID: input.sessionID,
tool: "bash",
tool: ShellID.ToolID,
callID: ulid(),
state: {
status: "running",
Expand Down
8 changes: 4 additions & 4 deletions packages/opencode/src/tool/registry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PlanExitTool } from "./plan"
import { Session } from "@/session/session"
import { QuestionTool } from "./question"
import { BashTool } from "./bash"
import { ShellTool } from "./shell"
import { EditTool } from "./edit"
import { GlobTool } from "./glob"
import { GrepTool } from "./grep"
Expand Down Expand Up @@ -106,7 +106,7 @@ export const layer: Layer.Layer<
const plan = yield* PlanExitTool
const webfetch = yield* WebFetchTool
const websearch = yield* WebSearchTool
const bash = yield* BashTool
const shell = yield* ShellTool
const globtool = yield* GlobTool
const writetool = yield* WriteTool
const edit = yield* EditTool
Expand Down Expand Up @@ -195,7 +195,7 @@ export const layer: Layer.Layer<

const tool = yield* Effect.all({
invalid: Tool.init(invalid),
bash: Tool.init(bash),
shell: Tool.init(shell),
read: Tool.init(read),
glob: Tool.init(globtool),
grep: Tool.init(greptool),
Expand All @@ -217,7 +217,7 @@ export const layer: Layer.Layer<
builtin: [
tool.invalid,
...(questionEnabled ? [tool.question] : []),
tool.bash,
tool.shell,
tool.read,
tool.glob,
tool.grep,
Expand Down
Loading
Loading