Sfoglia il codice sorgente

fix: handle redirected_statement treesitter node in bash permissions (#6737)

Patrick Schiel 2 settimane fa
parent
commit
e7ff7143b6

+ 6 - 2
packages/opencode/src/tool/bash.ts

@@ -91,6 +91,10 @@ export const BashTool = Tool.define("bash", async () => {
 
       for (const node of tree.rootNode.descendantsOfType("command")) {
         if (!node) continue
+
+        // Get full command text including redirects if present
+        let commandText = node.parent?.type === "redirected_statement" ? node.parent.text : node.text
+
         const command = []
         for (let i = 0; i < node.childCount; i++) {
           const child = node.child(i)
@@ -131,8 +135,8 @@ export const BashTool = Tool.define("bash", async () => {
 
         // cd covered by above check
         if (command.length && command[0] !== "cd") {
-          patterns.add(command.join(" "))
-          always.add(BashArity.prefix(command).join(" ") + "*")
+          patterns.add(commandText)
+          always.add(BashArity.prefix(command).join(" ") + " *")
         }
       }
 

+ 43 - 0
packages/opencode/test/tool/bash.test.ts

@@ -231,6 +231,49 @@ describe("tool.bash permissions", () => {
       },
     })
   })
+
+  test("matches redirects in permission pattern", async () => {
+    await using tmp = await tmpdir({ git: true })
+    await Instance.provide({
+      directory: tmp.path,
+      fn: async () => {
+        const bash = await BashTool.init()
+        const requests: Array<Omit<PermissionNext.Request, "id" | "sessionID" | "tool">> = []
+        const testCtx = {
+          ...ctx,
+          ask: async (req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) => {
+            requests.push(req)
+          },
+        }
+        await bash.execute({ command: "cat > /tmp/output.txt", description: "Redirect ls output" }, testCtx)
+        const bashReq = requests.find((r) => r.permission === "bash")
+        expect(bashReq).toBeDefined()
+        expect(bashReq!.patterns).toContain("cat > /tmp/output.txt")
+      },
+    })
+  })
+
+  test("always pattern has space before wildcard to not include different commands", async () => {
+    await using tmp = await tmpdir({ git: true })
+    await Instance.provide({
+      directory: tmp.path,
+      fn: async () => {
+        const bash = await BashTool.init()
+        const requests: Array<Omit<PermissionNext.Request, "id" | "sessionID" | "tool">> = []
+        const testCtx = {
+          ...ctx,
+          ask: async (req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) => {
+            requests.push(req)
+          },
+        }
+        await bash.execute({ command: "ls -la", description: "List" }, testCtx)
+        const bashReq = requests.find((r) => r.permission === "bash")
+        expect(bashReq).toBeDefined()
+        const pattern = bashReq!.always[0]
+        expect(pattern).toBe("ls *")
+      },
+    })
+  })
 })
 
 describe("tool.bash truncation", () => {