summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2025-11-09 18:21:38 -0800
committerGitHub <[email protected]>2025-11-09 20:21:38 -0600
commit4e549b1c05bc36b4f506f4be1548aaa147ac5032 (patch)
treedcb8961ea77c1b68c79c4377d168a07e09d725a7
parent7be8e16c333891d48f80483d80c8a33ceb9f49a9 (diff)
downloadopencode-4e549b1c05bc36b4f506f4be1548aaa147ac5032.tar.gz
opencode-4e549b1c05bc36b4f506f4be1548aaa147ac5032.zip
fix: allow user to configure doom loop & external dir perms (#4095)
-rw-r--r--packages/opencode/src/agent/agent.ts6
-rw-r--r--packages/opencode/src/config/config.ts4
-rw-r--r--packages/opencode/src/session/prompt.ts27
-rw-r--r--packages/opencode/src/tool/edit.ts29
-rw-r--r--packages/opencode/src/tool/patch.ts26
-rw-r--r--packages/opencode/src/tool/read.ts28
-rw-r--r--packages/opencode/src/tool/write.ts29
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts8
8 files changed, 95 insertions, 62 deletions
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index a6933708b..f1050ae72 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/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
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 7008141a0..eaac1dd4f 100644
--- a/packages/opencode/src/config/config.ts
+++ b/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(),
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 28afe61cb..10a9727a0 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/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
diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts
index ba3d2c0bf..96c62b86a 100644
--- a/packages/opencode/src/tool/edit.ts
+++ b/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 = ""
diff --git a/packages/opencode/src/tool/patch.ts b/packages/opencode/src/tool/patch.ts
index f76b9474c..0571cd353 100644
--- a/packages/opencode/src/tool/patch.ts
+++ b/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) {
diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts
index 963636fd1..4d8e15bfb 100644
--- a/packages/opencode/src/tool/read.ts
+++ b/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)
diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts
index acaa12392..58a0c177e 100644
--- a/packages/opencode/src/tool/write.ts
+++ b/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",
diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts
index abbce39b8..19f7a3a77 100644
--- a/packages/sdk/js/src/gen/types.gen.ts
+++ b/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