Просмотр исходного кода

fix: allow user to configure doom loop & external dir perms (#4095)

Aiden Cline 3 месяцев назад
Родитель
Сommit
4e549b1c05

+ 6 - 0
packages/opencode/src/agent/agent.ts

@@ -20,6 +20,8 @@ export namespace Agent {
         edit: Config.Permission,
         bash: z.record(z.string(), Config.Permission),
         webfetch: Config.Permission.optional(),
+        doom_loop: Config.Permission.optional(),
+        external_directory: Config.Permission.optional(),
       }),
       model: z
         .object({
@@ -45,6 +47,8 @@ export namespace Agent {
         "*": "allow",
       },
       webfetch: "allow",
+      doom_loop: "ask",
+      external_directory: "ask",
     }
     const agentPermission = mergeAgentPermissions(defaultPermission, cfg.permission ?? {})
 
@@ -244,6 +248,8 @@ function mergeAgentPermissions(basePermission: any, overridePermission: any): Ag
     edit: merged.edit ?? "allow",
     webfetch: merged.webfetch ?? "allow",
     bash: mergedBash ?? { "*": "allow" },
+    doom_loop: merged.doom_loop,
+    external_directory: merged.external_directory,
   }
 
   return result

+ 4 - 0
packages/opencode/src/config/config.ts

@@ -360,6 +360,8 @@ export namespace Config {
           edit: Permission.optional(),
           bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
           webfetch: Permission.optional(),
+          doom_loop: Permission.optional(),
+          external_directory: Permission.optional(),
         })
         .optional(),
     })
@@ -574,6 +576,8 @@ export namespace Config {
           edit: Permission.optional(),
           bash: z.union([Permission, z.record(z.string(), Permission)]).optional(),
           webfetch: Permission.optional(),
+          doom_loop: Permission.optional(),
+          external_directory: Permission.optional(),
         })
         .optional(),
       tools: z.record(z.string(), z.boolean()).optional(),

+ 15 - 12
packages/opencode/src/session/prompt.ts

@@ -1115,18 +1115,21 @@ export namespace SessionPrompt {
                         JSON.stringify(p.state.input) === JSON.stringify(value.input),
                     )
                   ) {
-                    await Permission.ask({
-                      type: "doom-loop",
-                      pattern: value.toolName,
-                      sessionID: assistantMsg.sessionID,
-                      messageID: assistantMsg.id,
-                      callID: value.toolCallId,
-                      title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
-                      metadata: {
-                        tool: value.toolName,
-                        input: value.input,
-                      },
-                    })
+                    const permission = await Agent.get(input.agent).then((x) => x.permission)
+                    if (permission.doom_loop === "ask") {
+                      await Permission.ask({
+                        type: "doom_loop",
+                        pattern: value.toolName,
+                        sessionID: assistantMsg.sessionID,
+                        messageID: assistantMsg.id,
+                        callID: value.toolCallId,
+                        title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
+                        metadata: {
+                          tool: value.toolName,
+                          input: value.input,
+                        },
+                      })
+                    }
                   }
                 }
                 break

+ 16 - 13
packages/opencode/src/tool/edit.ts

@@ -35,24 +35,27 @@ export const EditTool = Tool.define("edit", {
       throw new Error("oldString and newString must be different")
     }
 
+    const agent = await Agent.get(ctx.agent)
+
     const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
     if (!Filesystem.contains(Instance.directory, filePath)) {
       const parentDir = path.dirname(filePath)
-      await Permission.ask({
-        type: "external-directory",
-        pattern: parentDir,
-        sessionID: ctx.sessionID,
-        messageID: ctx.messageID,
-        callID: ctx.callID,
-        title: `Edit file outside working directory: ${filePath}`,
-        metadata: {
-          filepath: filePath,
-          parentDir,
-        },
-      })
+      if (agent.permission.external_directory === "ask") {
+        await Permission.ask({
+          type: "external_directory",
+          pattern: parentDir,
+          sessionID: ctx.sessionID,
+          messageID: ctx.messageID,
+          callID: ctx.callID,
+          title: `Edit file outside working directory: ${filePath}`,
+          metadata: {
+            filepath: filePath,
+            parentDir,
+          },
+        })
+      }
     }
 
-    const agent = await Agent.get(ctx.agent)
     let diff = ""
     let contentOld = ""
     let contentNew = ""

+ 14 - 12
packages/opencode/src/tool/patch.ts

@@ -55,18 +55,20 @@ export const PatchTool = Tool.define("patch", {
 
       if (!Filesystem.contains(Instance.directory, filePath)) {
         const parentDir = path.dirname(filePath)
-        await Permission.ask({
-          type: "external-directory",
-          pattern: parentDir,
-          sessionID: ctx.sessionID,
-          messageID: ctx.messageID,
-          callID: ctx.callID,
-          title: `Patch file outside working directory: ${filePath}`,
-          metadata: {
-            filepath: filePath,
-            parentDir,
-          },
-        })
+        if (agent.permission.external_directory === "ask") {
+          await Permission.ask({
+            type: "external_directory",
+            pattern: parentDir,
+            sessionID: ctx.sessionID,
+            messageID: ctx.messageID,
+            callID: ctx.callID,
+            title: `Patch file outside working directory: ${filePath}`,
+            metadata: {
+              filepath: filePath,
+              parentDir,
+            },
+          })
+        }
       }
 
       switch (hunk.type) {

+ 16 - 12
packages/opencode/src/tool/read.ts

@@ -10,6 +10,7 @@ import { Instance } from "../project/instance"
 import { Provider } from "../provider/provider"
 import { Identifier } from "../id/id"
 import { Permission } from "../permission"
+import { Agent } from "@/agent/agent"
 
 const DEFAULT_READ_LIMIT = 2000
 const MAX_LINE_LENGTH = 2000
@@ -27,21 +28,24 @@ export const ReadTool = Tool.define("read", {
       filepath = path.join(process.cwd(), filepath)
     }
     const title = path.relative(Instance.worktree, filepath)
+    const agent = await Agent.get(ctx.agent)
 
     if (!ctx.extra?.["bypassCwdCheck"] && !Filesystem.contains(Instance.directory, filepath)) {
       const parentDir = path.dirname(filepath)
-      await Permission.ask({
-        type: "external-directory",
-        pattern: parentDir,
-        sessionID: ctx.sessionID,
-        messageID: ctx.messageID,
-        callID: ctx.callID,
-        title: `Access file outside working directory: ${filepath}`,
-        metadata: {
-          filepath,
-          parentDir,
-        },
-      })
+      if (agent.permission.external_directory === "ask") {
+        await Permission.ask({
+          type: "external_directory",
+          pattern: parentDir,
+          sessionID: ctx.sessionID,
+          messageID: ctx.messageID,
+          callID: ctx.callID,
+          title: `Access file outside working directory: ${filepath}`,
+          metadata: {
+            filepath,
+            parentDir,
+          },
+        })
+      }
     }
 
     const file = Bun.file(filepath)

+ 16 - 13
packages/opencode/src/tool/write.ts

@@ -18,28 +18,31 @@ export const WriteTool = Tool.define("write", {
     filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
   }),
   async execute(params, ctx) {
+    const agent = await Agent.get(ctx.agent)
+
     const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
     if (!Filesystem.contains(Instance.directory, filepath)) {
       const parentDir = path.dirname(filepath)
-      await Permission.ask({
-        type: "external-directory",
-        pattern: parentDir,
-        sessionID: ctx.sessionID,
-        messageID: ctx.messageID,
-        callID: ctx.callID,
-        title: `Write file outside working directory: ${filepath}`,
-        metadata: {
-          filepath,
-          parentDir,
-        },
-      })
+      if (agent.permission.external_directory === "ask") {
+        await Permission.ask({
+          type: "external_directory",
+          pattern: parentDir,
+          sessionID: ctx.sessionID,
+          messageID: ctx.messageID,
+          callID: ctx.callID,
+          title: `Write file outside working directory: ${filepath}`,
+          metadata: {
+            filepath,
+            parentDir,
+          },
+        })
+      }
     }
 
     const file = Bun.file(filepath)
     const exists = await file.exists()
     if (exists) await FileTime.assert(ctx.sessionID, filepath)
 
-    const agent = await Agent.get(ctx.agent)
     if (agent.permission.edit === "ask")
       await Permission.ask({
         type: "write",

+ 8 - 0
packages/sdk/js/src/gen/types.gen.ts

@@ -198,6 +198,8 @@ export type AgentConfig = {
           [key: string]: "ask" | "allow" | "deny"
         }
     webfetch?: "ask" | "allow" | "deny"
+    doom_loop?: "ask" | "allow" | "deny"
+    external_directory?: "ask" | "allow" | "deny"
   }
   [key: string]:
     | unknown
@@ -216,6 +218,8 @@ export type AgentConfig = {
               [key: string]: "ask" | "allow" | "deny"
             }
         webfetch?: "ask" | "allow" | "deny"
+        doom_loop?: "ask" | "allow" | "deny"
+        external_directory?: "ask" | "allow" | "deny"
       }
     | undefined
 }
@@ -463,6 +467,8 @@ export type Config = {
           [key: string]: "ask" | "allow" | "deny"
         }
     webfetch?: "ask" | "allow" | "deny"
+    doom_loop?: "ask" | "allow" | "deny"
+    external_directory?: "ask" | "allow" | "deny"
   }
   tools?: {
     [key: string]: boolean
@@ -1043,6 +1049,8 @@ export type Agent = {
       [key: string]: "ask" | "allow" | "deny"
     }
     webfetch?: "ask" | "allow" | "deny"
+    doom_loop?: "ask" | "allow" | "deny"
+    external_directory?: "ask" | "allow" | "deny"
   }
   model?: {
     modelID: string