mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-09 10:24:11 +00:00
Compare commits
19 Commits
v1.1.45
...
bash-tweak
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a77f98ead | ||
|
|
c8c29faf4d | ||
|
|
e367c8439b | ||
|
|
e52d22039e | ||
|
|
9425eee09f | ||
|
|
320ebb35b7 | ||
|
|
775ddd8c46 | ||
|
|
32566a16a5 | ||
|
|
414bbd50ac | ||
|
|
b40bb950ae | ||
|
|
fcc256c3c9 | ||
|
|
bfbcf978fa | ||
|
|
c75dae680a | ||
|
|
639824b1ea | ||
|
|
8ede7c59d2 | ||
|
|
61cf0aeee2 | ||
|
|
ae328d338e | ||
|
|
2f5d6ba447 | ||
|
|
6cf5fe481b |
@@ -21,8 +21,7 @@ const MAX_OUTPUT_LENGTH = (() => {
|
||||
const parsed = Number(Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH)
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_OUTPUT_LENGTH
|
||||
})()
|
||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||
const MAX_TIMEOUT = 10 * 60 * 1000
|
||||
const DEFAULT_TIMEOUT = 2 * 60 * 1000
|
||||
const SIGKILL_TIMEOUT_MS = 200
|
||||
|
||||
export const log = Log.create({ service: "bash-tool" })
|
||||
@@ -90,6 +89,12 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
parameters: z.object({
|
||||
command: z.string().describe("The command to execute"),
|
||||
timeout: z.number().describe("Optional timeout in milliseconds").optional(),
|
||||
workdir: z
|
||||
.string()
|
||||
.describe(
|
||||
`The working directory to run the command in. Defaults to ${Instance.directory}. Use this instead of 'cd' commands.`,
|
||||
)
|
||||
.optional(),
|
||||
description: z
|
||||
.string()
|
||||
.describe(
|
||||
@@ -97,15 +102,47 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
),
|
||||
}),
|
||||
async execute(params, ctx) {
|
||||
const cwd = params.workdir || Instance.directory
|
||||
if (params.timeout !== undefined && params.timeout < 0) {
|
||||
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
|
||||
}
|
||||
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
||||
const timeout = params.timeout ?? DEFAULT_TIMEOUT
|
||||
const tree = await parser().then((p) => p.parse(params.command))
|
||||
if (!tree) {
|
||||
throw new Error("Failed to parse command")
|
||||
}
|
||||
const agent = await Agent.get(ctx.agent)
|
||||
|
||||
const checkExternalDirectory = async (dir: string) => {
|
||||
if (Filesystem.contains(Instance.directory, dir)) return
|
||||
const title = `This command references paths outside of ${Instance.directory}`
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: [dir, path.join(dir, "*")],
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title,
|
||||
metadata: {
|
||||
command: params.command,
|
||||
},
|
||||
})
|
||||
} else if (agent.permission.external_directory === "deny") {
|
||||
throw new Permission.RejectedError(
|
||||
ctx.sessionID,
|
||||
"external_directory",
|
||||
ctx.callID,
|
||||
{
|
||||
command: params.command,
|
||||
},
|
||||
`${title} so this command is not allowed to be executed.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await checkExternalDirectory(cwd)
|
||||
|
||||
const permissions = agent.permission.bash
|
||||
|
||||
const askPatterns = new Set<string>()
|
||||
@@ -144,32 +181,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
? resolved.replace(/^\/([a-z])\//, (_, drive) => `${drive.toUpperCase()}:\\`).replace(/\//g, "\\")
|
||||
: resolved
|
||||
|
||||
if (!Filesystem.contains(Instance.directory, normalized)) {
|
||||
const parentDir = path.dirname(normalized)
|
||||
if (agent.permission.external_directory === "ask") {
|
||||
await Permission.ask({
|
||||
type: "external_directory",
|
||||
pattern: [parentDir, path.join(parentDir, "*")],
|
||||
sessionID: ctx.sessionID,
|
||||
messageID: ctx.messageID,
|
||||
callID: ctx.callID,
|
||||
title: `This command references paths outside of ${Instance.directory}`,
|
||||
metadata: {
|
||||
command: params.command,
|
||||
},
|
||||
})
|
||||
} else if (agent.permission.external_directory === "deny") {
|
||||
throw new Permission.RejectedError(
|
||||
ctx.sessionID,
|
||||
"external_directory",
|
||||
ctx.callID,
|
||||
{
|
||||
command: params.command,
|
||||
},
|
||||
`This command references paths outside of ${Instance.directory} so it is not allowed to be executed.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
await checkExternalDirectory(normalized)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,7 +227,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
|
||||
const proc = spawn(params.command, {
|
||||
shell,
|
||||
cwd: Instance.directory,
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
},
|
||||
|
||||
@@ -7,10 +7,10 @@ Before executing the command, please follow these steps:
|
||||
- For example, before running "mkdir foo/bar", first use List to check that "foo" exists and is the intended parent directory
|
||||
|
||||
2. Command Execution:
|
||||
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
||||
- Always quote file paths that contain spaces with double quotes (e.g., rm "path with spaces/file.txt")
|
||||
- Examples of proper quoting:
|
||||
- cd "/Users/name/My Documents" (correct)
|
||||
- cd /Users/name/My Documents (incorrect - will fail)
|
||||
- mkdir "/Users/name/My Documents" (correct)
|
||||
- mkdir /Users/name/My Documents (incorrect - will fail)
|
||||
- python "/path/with spaces/script.py" (correct)
|
||||
- python /path/with spaces/script.py (incorrect - will fail)
|
||||
- After ensuring proper quoting, execute the command.
|
||||
@@ -18,20 +18,27 @@ Before executing the command, please follow these steps:
|
||||
|
||||
Usage notes:
|
||||
- The command argument is required.
|
||||
- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 120000ms (2 minutes).
|
||||
- You can specify an optional timeout in milliseconds. If not specified, commands will timeout after 120000ms (2 minutes). Use the `timeout` parameter to control execution time.
|
||||
- The `workdir` parameter specifies the working directory for the command. Defaults to the current working directory. Prefer setting `workdir` over using `cd` in your commands.
|
||||
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
|
||||
- If the output exceeds 30000 characters, output will be truncated before being returned to you.
|
||||
- VERY IMPORTANT: You MUST avoid using search commands like `find` and `grep`. Instead use Grep, Glob, or Task to search. You MUST avoid read tools like `cat`, `head`, `tail`, and `ls`, and use Read and List to read files.
|
||||
- If you _still_ need to run `grep`, STOP. ALWAYS USE ripgrep at `rg` (or /usr/bin/rg) first, which all opencode users have pre-installed.
|
||||
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).
|
||||
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it.
|
||||
<good-example>
|
||||
pytest /foo/bar/tests
|
||||
</good-example>
|
||||
<bad-example>
|
||||
cd /foo/bar && pytest tests
|
||||
</bad-example>
|
||||
|
||||
# Working Directory
|
||||
|
||||
The `workdir` parameter sets the working directory for command execution. Prefer using `workdir` over `cd <dir> &&` command chains when you simply need to run a command in a different directory.
|
||||
|
||||
<good-example>
|
||||
workdir="/foo/bar", command="pytest tests"
|
||||
</good-example>
|
||||
<good-example>
|
||||
command="pytest /foo/bar/tests"
|
||||
</good-example>
|
||||
<bad-example>
|
||||
command="cd /foo/bar && pytest tests"
|
||||
</bad-example>
|
||||
|
||||
# Committing changes with git
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ const ctx = {
|
||||
metadata: () => {},
|
||||
}
|
||||
|
||||
const bash = await BashTool.init()
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
|
||||
describe("tool.bash", () => {
|
||||
@@ -21,6 +20,7 @@ describe("tool.bash", () => {
|
||||
await Instance.provide({
|
||||
directory: projectRoot,
|
||||
fn: async () => {
|
||||
const bash = await BashTool.init()
|
||||
const result = await bash.execute(
|
||||
{
|
||||
command: "echo 'test'",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6190
packages/sdk/stainless/openapi.json
Normal file
6190
packages/sdk/stainless/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user