Skip to content

Commit 5861452

Browse files
committed
Fixes #1070: Rename internal bash tool to terminal for cross-platform accuracy
1 parent 03b9459 commit 5861452

95 files changed

Lines changed: 628 additions & 617 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/core/src/tool/builtins.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export * as BuiltInTools from "./builtins"
22

33
import { Layer } from "effect"
4-
import { BashTool } from "./bash"
4+
import { TerminalTool } from "./terminal"
55
import { ApplyPatchTool } from "./apply-patch"
66
import { EditTool } from "./edit"
77
import { GlobTool } from "./glob"
@@ -30,7 +30,7 @@ import { WriteTool } from "./write"
3030
*/
3131
export const locationLayer = Layer.mergeAll(
3232
ApplyPatchTool.layer,
33-
BashTool.layer,
33+
TerminalTool.layer,
3434
EditTool.layer,
3535
GlobTool.layer,
3636
GrepTool.layer,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * as BashTool from "./bash"
1+
export * as TerminalTool from "./terminal"
22

33
import path from "path"
44
import { ToolFailure } from "@opencode-ai/llm"
@@ -13,7 +13,7 @@ import { PositiveInt } from "../schema"
1313
import { Tool } from "./tool"
1414
import { Tools } from "./tools"
1515

16-
export const name = "bash"
16+
export const name = "terminal"
1717
export const DEFAULT_TIMEOUT_MS = 2 * 60 * 1_000
1818
export const MAX_TIMEOUT_MS = 10 * 60 * 1_000
1919
export const MAX_CAPTURE_BYTES = 1024 * 1024

‎packages/core/src/v1/config/permission.ts‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ const InputObject = Schema.StructWithRest(
2121
glob: Schema.optional(Rule),
2222
grep: Schema.optional(Rule),
2323
list: Schema.optional(Rule),
24-
bash: Schema.optional(Rule),
24+
bash: Schema.optional(Rule), // kept for backward compatibility but mapped to "terminal"
25+
terminal: Schema.optional(Rule),
2526
task: Schema.optional(Rule),
2627
external_directory: Schema.optional(Rule),
2728
todowrite: Schema.optional(Action),
@@ -37,8 +38,14 @@ const InputObject = Schema.StructWithRest(
3738

3839
const InputSchema = Schema.Union([Action, InputObject])
3940

40-
const normalizeInput = (input: Schema.Schema.Type<typeof InputSchema>): Schema.Schema.Type<typeof InputObject> =>
41-
typeof input === "string" ? { "*": input } : input
41+
const normalizeInput = (input: Schema.Schema.Type<typeof InputSchema>): Schema.Schema.Type<typeof InputObject> => {
42+
if (typeof input === "string") return { "*": input }
43+
const result = { ...input }
44+
if (result.terminal !== undefined && result.bash !== undefined) {
45+
delete (result as any).bash
46+
}
47+
return result
48+
}
4249

4350
export const Info = InputSchema.pipe(
4451
Schema.decodeTo(InputObject, {

‎packages/core/test/config/agent.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ describe("ConfigAgentPlugin.Plugin", () => {
8181
{ action: "read", resource: "*", effect: "allow" },
8282
{ action: "bash", resource: "git *", effect: "allow" },
8383
])
84-
expect(PermissionV2.evaluate("bash", "git status", buildAgent.permissions).effect).toBe("allow")
85-
expect(PermissionV2.evaluate("bash", "bun test", buildAgent.permissions).effect).toBe("ask")
84+
expect(PermissionV2.evaluate("terminal", "git status", buildAgent.permissions).effect).toBe("allow")
85+
expect(PermissionV2.evaluate("terminal", "bun test", buildAgent.permissions).effect).toBe("ask")
8686

8787
const reviewer = yield* agents.get(AgentV2.ID.make("reviewer"))
8888
if (!reviewer) throw new Error("expected configured reviewer agent")

‎packages/core/test/session-runner-tool-registry.test.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ describe("ToolRegistry", () => {
6666
const names = (rules: Parameters<ToolRegistry.Interface["materialize"]>[0]) =>
6767
toolDefinitions(service, rules).pipe(Effect.map((definitions) => definitions.map((tool) => tool.name)))
6868

69-
expect(yield* names([{ action: "question", resource: "*", effect: "deny" }])).toEqual([
70-
"bash",
69+
expect(yield* names([{ action: "question", resource: "*", effect: "deny" }])).toEqual(["terminal",
7170
"edit",
7271
"write",
7372
"apply_patch",
@@ -84,7 +83,7 @@ describe("ToolRegistry", () => {
8483
{ action: "*", resource: "*", effect: "deny" },
8584
]),
8685
).toEqual([])
87-
expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual(["question", "bash"])
86+
expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual(["question", "terminal"])
8887
}),
8988
)
9089

‎packages/core/test/session-tool-progress.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@ describe("Tool.Progress", () => {
7474
timestamp,
7575
assistantMessageID,
7676
callID,
77-
name: "bash",
77+
name: "terminal",
7878
})
7979
yield* service.publish(SessionEvent.Tool.Called, {
8080
sessionID,
8181
timestamp,
8282
assistantMessageID,
8383
callID,
84-
tool: "bash",
84+
tool: "terminal",
8585
input: { command: "pwd" },
8686
provider: { executed: false },
8787
})
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { PermissionV2 } from "@opencode-ai/core/permission"
1212
import { AppProcess } from "@opencode-ai/core/process"
1313
import { AbsolutePath } from "@opencode-ai/core/schema"
1414
import { SessionV2 } from "@opencode-ai/core/session"
15-
import { BashTool } from "@opencode-ai/core/tool/bash"
15+
import { TerminalTool } from "@opencode-ai/core/tool/terminal"
1616
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
1717
import { location } from "./fixture/location"
1818
import { tmpdir } from "./fixture/tmpdir"
@@ -102,7 +102,7 @@ const withTool = <A, E, R>(
102102
)
103103
const mutation = LocationMutation.layer.pipe(Layer.provide(filesystem), Layer.provide(activeLocation))
104104
const registry = ToolRegistry.defaultLayer.pipe(Layer.provide(permission))
105-
const bash = BashTool.layer.pipe(
105+
const bash = TerminalTool.layer.pipe(
106106
Layer.provide(registry),
107107
Layer.provide(permission),
108108
Layer.provide(mutation),
@@ -115,15 +115,15 @@ const withTool = <A, E, R>(
115115
}).pipe(Effect.provide(Layer.mergeAll(registry, bash)))
116116
}
117117

118-
const call = (input: typeof BashTool.Input.Type, id = "call-bash") => ({
118+
const call = (input: typeof TerminalTool.Input.Type, id = "call-bash") => ({
119119
sessionID,
120120
...toolIdentity,
121-
call: { type: "tool-call" as const, id, name: "bash", input },
121+
call: { type: "tool-call" as const, id, name: "terminal", input },
122122
})
123123

124124
const it = testEffect(Layer.empty)
125125

126-
describe("BashTool", () => {
126+
describe("TerminalTool", () => {
127127
it.live("registers and returns structured successful output from the active Location", () =>
128128
Effect.acquireUseRelease(
129129
Effect.promise(() => tmpdir()),
@@ -132,9 +132,9 @@ describe("BashTool", () => {
132132
return withTool(tmp.path, (registry) =>
133133
Effect.gen(function* () {
134134
const definitions = yield* toolDefinitions(registry)
135-
expect(definitions.map((tool) => tool.name)).toEqual(["bash"])
135+
expect(definitions.map((tool) => tool.name)).toEqual(["terminal"])
136136
expect(definitions[0]?.inputSchema).not.toHaveProperty("properties.background")
137-
expect(yield* toolDefinitions(registry, [{ action: "bash", resource: "*", effect: "deny" }])).toEqual([])
137+
expect(yield* toolDefinitions(registry, [{ action: "terminal", resource: "*", effect: "deny" }])).toEqual([])
138138
expect(
139139
yield* settleTool(registry, call({ command: "pwd", description: "Print working directory" })),
140140
).toEqual({
@@ -152,10 +152,10 @@ describe("BashTool", () => {
152152
})
153153
expect(runs).toMatchObject([{ command: "pwd", cwd: realpathSync(tmp.path) }])
154154
expect(runs[0]?.options).toMatchObject({
155-
maxOutputBytes: BashTool.MAX_CAPTURE_BYTES,
156-
maxErrorBytes: BashTool.MAX_CAPTURE_BYTES,
155+
maxOutputBytes: TerminalTool.MAX_CAPTURE_BYTES,
156+
maxErrorBytes: TerminalTool.MAX_CAPTURE_BYTES,
157157
})
158-
expect(assertions).toMatchObject([{ sessionID, action: "bash", resources: ["pwd"], save: ["pwd"] }])
158+
expect(assertions).toMatchObject([{ sessionID, action: "terminal", resources: ["pwd"], save: ["pwd"] }])
159159
}),
160160
)
161161
},
@@ -188,7 +188,7 @@ describe("BashTool", () => {
188188
reset()
189189
const workdir = path.join(tmp.path, "src")
190190
afterPermission = (input) =>
191-
input.action === "bash"
191+
input.action === "terminal"
192192
? Effect.promise(async () => {
193193
await fs.rm(workdir, { recursive: true })
194194
await fs.writeFile(workdir, "not a directory")
@@ -201,7 +201,7 @@ describe("BashTool", () => {
201201
Effect.andThen(
202202
Effect.sync(() => {
203203
expect(runs).toEqual([])
204-
expect(assertions.map((input) => input.action)).toEqual(["bash"])
204+
expect(assertions.map((input) => input.action)).toEqual(["terminal"])
205205
}),
206206
),
207207
)
@@ -249,7 +249,7 @@ describe("BashTool", () => {
249249
).pipe(
250250
Effect.andThen(
251251
Effect.sync(() => {
252-
expect(assertions.map((item) => item.action)).toEqual(["external_directory", "bash"])
252+
expect(assertions.map((item) => item.action)).toEqual(["external_directory", "terminal"])
253253
expect(assertions[0]).toMatchObject({
254254
resources: [path.join(realpathSync(outside.path), "*").replaceAll("\\", "/")],
255255
})
@@ -281,7 +281,7 @@ describe("BashTool", () => {
281281
reset()
282282
denyAction = "bash"
283283
yield* withTool(active.path, (registry) => executeTool(registry, call({ command: "pwd" })))
284-
expect(assertions.map((item) => item.action)).toEqual(["bash"])
284+
expect(assertions.map((item) => item.action)).toEqual(["terminal"])
285285
expect(runs).toEqual([])
286286
}),
287287
([active, outside]) =>
@@ -301,7 +301,7 @@ describe("BashTool", () => {
301301
return withTool(active.path, (registry) => settleTool(registry, call({ command: `cat ${target}` }))).pipe(
302302
Effect.andThen((settled) =>
303303
Effect.sync(() => {
304-
expect(assertions.map((item) => item.action)).toEqual(["bash"])
304+
expect(assertions.map((item) => item.action)).toEqual(["terminal"])
305305
expect(runs).toHaveLength(1)
306306
expect(settled.output?.structured).toMatchObject({
307307
warnings: [
@@ -399,7 +399,7 @@ describe("BashTool", () => {
399399
})
400400

401401
test("keeps locked deferred parity TODOs visible", async () => {
402-
const source = await fs.readFile(new URL("../src/tool/bash.ts", import.meta.url), "utf8")
402+
const source = await fs.readFile(new URL("../src/tool/terminal.ts", import.meta.url), "utf8")
403403
for (const todo of [
404404
"Port tree-sitter bash / PowerShell parser-based approval reduction.",
405405
"Port BashArity reusable command-prefix approvals.",

‎packages/opencode/specs/v2/api.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const opencode = OpenCode.make({})
88
opencode.tool.add(ReadTool)
99

1010
opencode.tool.add({
11-
name: "bash",
11+
name: "terminal",
1212
schema: {
1313
type: "object",
1414
properties: {

‎packages/opencode/src/acp/event.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export class Subscription {
281281
private async runningTool(sessionId: string, part: ToolPart, cwd: string) {
282282
if (part.state.status !== "running") return
283283

284-
const output = part.tool === "bash" ? shellOutputSnapshot(part.state) : undefined
284+
const output = part.tool === "terminal" ? shellOutputSnapshot(part.state) : undefined
285285
if (output !== undefined) {
286286
if (this.shellSnapshots.get(part.callID) === output) {
287287
await this.input.connection.sessionUpdate({

‎packages/opencode/src/acp/tool.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ function shellCommand(input: ToolInput) {
296296

297297
function isShell(toolName: string) {
298298
const tool = toolName.toLocaleLowerCase()
299-
return tool === "bash" || tool === "shell"
299+
return tool === "terminal" || tool === "shell"
300300
}
301301

302302
export const mapToolKind = toToolKind

0 commit comments

Comments
 (0)