Skip to content
Open
Changes from all commits
Commits
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
61 changes: 60 additions & 1 deletion packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
/**
* 文件用途:Bash工具实现,提供命令执行、权限管理、输出截断等功能
* 作者:TRAE
* 创建日期:2026-02-01
*
* 输入输出签名:
* - 输入:command(命令字符串)、timeout(超时毫秒)、workdir(工作目录)、description(命令描述)
* - 输出:{ title, metadata: { output, exit, description }, output }
*
* 依赖列表:
* - zod@latest
* - web-tree-sitter@latest
* - tree-sitter-bash@latest
* - bun@latest
*
* 与其他模块交互方式:
* - 调用Instance.directory获取项目根目录
* - 调用ctx.ask()请求权限(external_directory、bash)
* - 调用ctx.metadata()更新元数据
* - 调用ctx.abort监听中止事件
* - 调用Shell.killTree()终止进程树
*
* 其他备注:
* - 相关文件:bash.txt(工具描述模板)
* - 支持Windows、Linux、macOS多平台
*/
import z from "zod"
import { spawn } from "child_process"
import { Tool } from "./tool"
Expand All @@ -22,6 +48,35 @@ const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2

export const log = Log.create({ service: "bash-tool" })

const WINDOWS_RESERVED_DEVICE_NAMES = new Set([
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
])

/**
* 实现说明:Windows保留设备名称检查
*
* Windows系统保留以下设备名称,不能作为文件名使用:
* - CON, PRN, AUX, NUL(标准设备)
* - COM1-9(串行端口)
* - LPT1-9(并行端口)
*
* 检查逻辑:
* 1. 提取路径的基本名称(去除目录部分)
* 2. 转换为大写(Windows文件系统不区分大小写)
* 3. 检查是否在保留名称集合中
*
* 注意事项:
* - 此检查应在调用realpath之前进行,避免无效命令
* - 只检查基本名称,不检查路径中间的保留名称
* - 适用于Windows平台(process.platform === 'win32')
*/
const isWindowsReservedDeviceName = (name: string): boolean => {
const baseName = path.basename(name).toUpperCase()
return WINDOWS_RESERVED_DEVICE_NAMES.has(baseName)
}

const resolveWasm = (asset: string) => {
if (asset.startsWith("file://")) return fileURLToPath(asset)
if (asset.startsWith("/") || /^[a-z]:/i.test(asset)) return asset
Expand Down Expand Up @@ -71,7 +126,7 @@ export const BashTool = Tool.define("bash", async () => {
description: z
.string()
.describe(
"Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'",
"Clear, concise description of what this command does in 5-10 words",
),
}),
async execute(params, ctx) {
Expand Down Expand Up @@ -115,6 +170,10 @@ export const BashTool = Tool.define("bash", async () => {
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown", "cat"].includes(command[0])) {
for (const arg of command.slice(1)) {
if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue
if (process.platform === "win32" && isWindowsReservedDeviceName(arg)) {
log.info("skipping Windows reserved device name", { arg })
continue
}
const resolved = await $`realpath ${arg}`
.cwd(cwd)
.quiet()
Expand Down
Loading