diff --git a/packages/opencode/specs/effect-migration.md b/packages/opencode/specs/effect-migration.md index 31fcac19b..4d99b4841 100644 --- a/packages/opencode/specs/effect-migration.md +++ b/packages/opencode/specs/effect-migration.md @@ -266,7 +266,6 @@ Individual tools, ordered by value: - [ ] `batch.ts` — MEDIUM: parallel execution, per-call error recovery → Effect.all - [ ] `task.ts` — MEDIUM: task state management - [ ] `ls.ts` — MEDIUM: bounded directory listing over ripgrep-backed traversal -- [ ] `multiedit.ts` — MEDIUM: sequential edit orchestration over `edit.ts` - [ ] `glob.ts` — LOW: simple async generator - [ ] `lsp.ts` — LOW: dispatch switch over LSP operations - [ ] `question.ts` — LOW: prompt wrapper diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 6a03c26b8..46989cd68 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -108,7 +108,7 @@ const normalize = (agent: AgentInput): AgentInfo => { const permission: ConfigPermission.Info = {} for (const [tool, enabled] of Object.entries(tools ?? {})) { const action = enabled ? "allow" : "deny" - if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") { + if (tool === "write" || tool === "edit" || tool === "patch") { permission.edit = action continue } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 97348fa1a..287a48580 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -730,7 +730,7 @@ const rawLayer = Layer.effect( const perms: Record = {} for (const [tool, enabled] of Object.entries(result.tools)) { const action: ConfigPermission.Action = enabled ? "allow" : "deny" - if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") { + if (tool === "write" || tool === "edit" || tool === "patch") { perms.edit = action continue } diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index a45aaf59d..ea41bfa49 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -294,7 +294,7 @@ export namespace Permission { return rulesets.flat() } - const EDIT_TOOLS = ["edit", "write", "apply_patch", "multiedit"] + const EDIT_TOOLS = ["edit", "write", "apply_patch"] export function disabled(tools: string[], ruleset: Ruleset): Set { const result = new Set() diff --git a/packages/opencode/src/tool/bash.txt b/packages/opencode/src/tool/bash.txt index 668cea307..97189499c 100644 --- a/packages/opencode/src/tool/bash.txt +++ b/packages/opencode/src/tool/bash.txt @@ -13,7 +13,7 @@ Before executing the command, please follow these steps: - For example, before running "mkdir foo/bar", first use `ls foo` 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., rm "path with spaces/file.txt") + - Always quote file paths that contain spaces with double quotes (e.g., mkdir "/Users/name/path with spaces") - Examples of proper quoting: - mkdir "/Users/name/My Documents" (correct) - mkdir /Users/name/My Documents (incorrect - will fail) @@ -35,6 +35,7 @@ Usage notes: - Edit files: Use Edit (NOT sed/awk) - Write files: Use Write (NOT echo >/cat < Important: -- DO NOT use the TodoWrite or Task tools - Return the PR URL when you're done, so the user can see it # Other common operations diff --git a/packages/opencode/src/tool/edit.txt b/packages/opencode/src/tool/edit.txt index 618fd5ad1..cf3c52f20 100644 --- a/packages/opencode/src/tool/edit.txt +++ b/packages/opencode/src/tool/edit.txt @@ -8,3 +8,4 @@ Usage: - The edit will FAIL if `oldString` is not found in the file with an error "oldString not found in content". - The edit will FAIL if `oldString` is found multiple times in the file with an error "Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match." Either provide a larger string with more surrounding context to make it unique or use `replaceAll` to change every instance of `oldString`. - Use `replaceAll` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance. +- To delete a line cleanly, include the trailing newline character (`\n`) in `oldString` and pass an empty string as `newString` (e.g., `oldString: "line to delete\n"`, `newString: ""`). Without that trailing newline, the line content is removed but a stray blank line remains at the deleted position. diff --git a/packages/opencode/src/tool/glob.txt b/packages/opencode/src/tool/glob.txt index 627da6cae..057b7981c 100644 --- a/packages/opencode/src/tool/glob.txt +++ b/packages/opencode/src/tool/glob.txt @@ -3,4 +3,4 @@ - Returns matching file paths sorted by modification time - Use this tool when you need to find files by name patterns - When you are doing an open-ended search that may require multiple rounds of globbing and grepping, use the Task tool instead -- You have the capability to call multiple tools in a single response. It is always better to speculatively perform multiple searches as a batch that are potentially useful. +- You have the capability to call multiple tools in a single response. diff --git a/packages/opencode/src/tool/multiedit.ts b/packages/opencode/src/tool/multiedit.ts deleted file mode 100644 index 004d3c870..000000000 --- a/packages/opencode/src/tool/multiedit.ts +++ /dev/null @@ -1,61 +0,0 @@ -import z from "zod" -import { Effect } from "effect" -import * as Tool from "./tool" -import { EditTool } from "./edit" -import DESCRIPTION from "./multiedit.txt" -import path from "path" -import { Instance } from "../project/instance" - -export const MultiEditTool = Tool.define( - "multiedit", - Effect.gen(function* () { - const editInfo = yield* EditTool - const edit = yield* editInfo.init() - - return { - description: DESCRIPTION, - parameters: z.object({ - filePath: z.string().describe("The absolute path to the file to modify"), - edits: z - .array( - z.object({ - filePath: z.string().describe("The absolute path to the file to modify"), - oldString: z.string().describe("The text to replace"), - newString: z.string().describe("The text to replace it with (must be different from oldString)"), - replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"), - }), - ) - .describe("Array of edit operations to perform sequentially on the file"), - }), - execute: ( - params: { - filePath: string - edits: Array<{ filePath: string; oldString: string; newString: string; replaceAll?: boolean }> - }, - ctx: Tool.Context, - ) => - Effect.gen(function* () { - const results = [] - for (const [, entry] of params.edits.entries()) { - const result = yield* edit.execute( - { - filePath: params.filePath, - oldString: entry.oldString, - newString: entry.newString, - replaceAll: entry.replaceAll, - }, - ctx, - ) - results.push(result) - } - return { - title: path.relative(Instance.worktree, params.filePath), - metadata: { - results: results.map((r) => r.metadata), - }, - output: results.at(-1)!.output, - } - }), - } - }), -) diff --git a/packages/opencode/src/tool/multiedit.txt b/packages/opencode/src/tool/multiedit.txt deleted file mode 100644 index bb4815124..000000000 --- a/packages/opencode/src/tool/multiedit.txt +++ /dev/null @@ -1,41 +0,0 @@ -This is a tool for making multiple edits to a single file in one operation. It is built on top of the Edit tool and allows you to perform multiple find-and-replace operations efficiently. Prefer this tool over the Edit tool when you need to make multiple edits to the same file. - -Before using this tool: - -1. Use the Read tool to understand the file's contents and context -2. Verify the directory path is correct - -To make multiple file edits, provide the following: -1. file_path: The absolute path to the file to modify (must be absolute, not relative) -2. edits: An array of edit operations to perform, where each edit contains: - - oldString: The text to replace (must match the file contents exactly, including all whitespace and indentation) - - newString: The edited text to replace the oldString - - replaceAll: Replace all occurrences of oldString. This parameter is optional and defaults to false. - -IMPORTANT: -- All edits are applied in sequence, in the order they are provided -- Each edit operates on the result of the previous edit -- All edits must be valid for the operation to succeed - if any edit fails, none will be applied -- This tool is ideal when you need to make several changes to different parts of the same file - -CRITICAL REQUIREMENTS: -1. All edits follow the same requirements as the single Edit tool -2. The edits are atomic - either all succeed or none are applied -3. Plan your edits carefully to avoid conflicts between sequential operations - -WARNING: -- The tool will fail if edits.oldString doesn't match the file contents exactly (including whitespace) -- The tool will fail if edits.oldString and edits.newString are the same -- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find - -When making edits: -- Ensure all edits result in idiomatic, correct code -- Do not leave the code in a broken state -- Always use absolute file paths (starting with /) -- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked. -- Use replaceAll for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance. - -If you want to create a new file, use: -- A new file path, including dir name if needed -- First edit: empty oldString and the new file's contents as newString -- Subsequent edits: normal edit operations on the created content diff --git a/packages/opencode/src/tool/skill.txt b/packages/opencode/src/tool/skill.txt index 44d990317..0bac218a8 100644 --- a/packages/opencode/src/tool/skill.txt +++ b/packages/opencode/src/tool/skill.txt @@ -1,5 +1,5 @@ -Load a specialized skill when the task at hand matches one of the skills listed in the system prompt. +Load a specialized skill when the task at hand matches one of the skills listed below. Use this tool to inject the skill's instructions and resources into current conversation. The output may contain detailed workflow guidance as well as references to scripts, files, etc in the same directory as the skill. -The skill name must match one of the skills listed in your system prompt. +The skill name must match one of the skills listed below. diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index fba8470d1..95b193534 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -9,49 +9,11 @@ When NOT to use the Task tool: - If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly - If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly - If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly -- Other tasks that are not related to the agent descriptions above +- Other tasks that are not related to the available agents Usage notes: -1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses -2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session. -3. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you. -4. The agent's outputs should generally be trusted -5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands). -6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement. - -Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above): - - -"code-reviewer": use this agent after you are done writing a significant piece of code -"greeting-responder": use this agent when to respond to user greetings with a friendly joke - - - -user: "Please write a function that checks if a number is prime" -assistant: Sure let me write a function that checks if a number is prime -assistant: First let me use the Write tool to write a function that checks if a number is prime -assistant: I'm going to use the Write tool to write the following code: - -function isPrime(n) { - if (n <= 1) return false - for (let i = 2; i * i <= n; i++) { - if (n % i === 0) return false - } - return true -} - - -Since a significant piece of code was written and the task was completed, now use the code-reviewer agent to review the code - -assistant: Now let me use the code-reviewer agent to review the code -assistant: Uses the Task tool to launch the code-reviewer agent - - - -user: "Hello" - -Since the user is greeting, use the greeting-responder agent to respond with a friendly joke - -assistant: "I'm going to use the Task tool to launch the with the greeting-responder agent" - +1. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session. +2. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you. +3. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands). +4. If the proactive trigger applies to the available agents, you should try your best to use it without the user having to ask for it first. Use your judgement. diff --git a/packages/opencode/src/tool/todowrite.txt b/packages/opencode/src/tool/todowrite.txt index 2737cd18b..36098501b 100644 --- a/packages/opencode/src/tool/todowrite.txt +++ b/packages/opencode/src/tool/todowrite.txt @@ -1,4 +1,4 @@ -Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. +Use this tool to create and manage a structured task list for your current coding session. This helps you track progress and organize complex tasks. It also helps the user understand the progress of the task and overall progress of their requests. ## When to Use This Tool @@ -163,5 +163,3 @@ The assistant did not use the todo list because this is a single command executi - Break complex tasks into smaller, manageable steps - Use clear, descriptive task names -When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully. - diff --git a/packages/opencode/src/tool/trash.txt b/packages/opencode/src/tool/trash.txt index 167da065c..4b450cc8c 100644 --- a/packages/opencode/src/tool/trash.txt +++ b/packages/opencode/src/tool/trash.txt @@ -1,4 +1,7 @@ Moves a file or directory to the system Trash. -Use this instead of shell deletion commands like `rm`. +Use this for file or directory deletion. Trash is reversible; items moved to Trash can be restored from the system Trash UI. + +Do not use shell deletion commands. Those are permanent and unrecoverable. PawWork's permission system blocks the `rm`-family commands (`rm`, `rmdir`, `unlink`, `find -delete`) by default; Windows shell deletion (`del`, `erase`, `Remove-Item`) is not yet blocked but should still be avoided. + Accepts a single file or directory path, absolute or relative to the current project directory. diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index d39147b27..d77dba8d3 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -490,7 +490,7 @@ test("legacy tools config converts to permissions", async () => { }) }) -test("legacy tools config maps write/edit/patch/multiedit to edit permission", async () => { +test("legacy tools.write config maps to edit permission", async () => { await using tmp = await tmpdir({ config: { agent: { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 654f94bae..d1444ed4a 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -2003,35 +2003,6 @@ test("migrates legacy patch tool to edit permission", async () => { }) }) -test("migrates legacy multiedit tool to edit permission", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - agent: { - test: { - tools: { - multiedit: false, - }, - }, - }, - }), - ) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() - expect(config.agent?.["test"]?.permission).toEqual({ - edit: "deny", - }) - }, - }) -}) - test("migrates mixed legacy tools config", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 043e3257b..334d7d63c 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -387,9 +387,9 @@ test("disabled - disables tool when denied", () => { expect(result.has("read")).toBe(false) }) -test("disabled - disables edit/write/apply_patch/multiedit when edit denied", () => { +test("disabled - disables edit/write/apply_patch when edit denied", () => { const result = Permission.disabled( - ["edit", "write", "apply_patch", "multiedit", "bash"], + ["edit", "write", "apply_patch", "bash"], [ { permission: "*", pattern: "*", action: "allow" }, { permission: "edit", pattern: "*", action: "deny" }, @@ -398,7 +398,6 @@ test("disabled - disables edit/write/apply_patch/multiedit when edit denied", () expect(result.has("edit")).toBe(true) expect(result.has("write")).toBe(true) expect(result.has("apply_patch")).toBe(true) - expect(result.has("multiedit")).toBe(true) expect(result.has("bash")).toBe(false) })