Skip to content

Commit 963f407

Browse files
committed
tui: improve permission error handling and evaluation logic
1 parent 4f1ef93 commit 963f407

File tree

3 files changed

+24
-14
lines changed

3 files changed

+24
-14
lines changed

.opencode/opencode.jsonc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
"options": {},
1111
},
1212
},
13+
"permission": {
14+
"bash": {
15+
"ls foo": "ask",
16+
},
17+
},
1318
"mcp": {
1419
"context7": {
1520
"type": "remote",

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ function InlineTool(props: { icon: string; complete: any; pending: string; child
14061406

14071407
const error = createMemo(() => (props.part.state.status === "error" ? props.part.state.error : undefined))
14081408

1409-
const denied = createMemo(() => error()?.includes("rejected permission"))
1409+
const denied = createMemo(() => error()?.includes("rejected permission") || error()?.includes("specified a rule"))
14101410

14111411
return (
14121412
<box

packages/opencode/src/permission/next.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,11 @@ export namespace PermissionNext {
121121
const s = await state()
122122
const { ruleset, ...request } = input
123123
for (const pattern of request.patterns ?? []) {
124-
const action = evaluate(request.permission, pattern, ruleset, s.approved)
125-
log.info("evaluated", { permission: request.permission, pattern, action })
126-
if (action === "deny") throw new RejectedError()
127-
if (action === "ask") {
124+
const rule = evaluate(request.permission, pattern, ruleset, s.approved)
125+
log.info("evaluated", { permission: request.permission, pattern, action: rule })
126+
if (rule.action === "deny")
127+
throw new AutoRejectedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission)))
128+
if (rule.action === "ask") {
128129
const id = input.id ?? Identifier.ascending("permission")
129130
return new Promise<void>((resolve, reject) => {
130131
const info: Request = {
@@ -139,7 +140,7 @@ export namespace PermissionNext {
139140
Bus.publish(Event.Asked, info)
140141
})
141142
}
142-
if (action === "allow") continue
143+
if (rule.action === "allow") continue
143144
}
144145
},
145146
)
@@ -195,7 +196,7 @@ export namespace PermissionNext {
195196
for (const [id, pending] of Object.entries(s.pending)) {
196197
if (pending.info.sessionID !== sessionID) continue
197198
const ok = pending.info.patterns.every(
198-
(pattern) => evaluate(pending.info.permission, pattern, s.approved) === "allow",
199+
(pattern) => evaluate(pending.info.permission, pattern, s.approved).action === "allow",
199200
)
200201
if (!ok) continue
201202
delete s.pending[id]
@@ -215,13 +216,13 @@ export namespace PermissionNext {
215216
},
216217
)
217218

218-
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Action {
219+
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
219220
const merged = merge(...rulesets)
220221
log.info("evaluate", { permission, pattern, ruleset: merged })
221222
const match = merged.findLast(
222223
(rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
223224
)
224-
return match?.action ?? "ask"
225+
return match ?? { action: "allow", permission, pattern: "*" }
225226
}
226227

227228
const EDIT_TOOLS = ["edit", "write", "patch", "multiedit"]
@@ -230,19 +231,23 @@ export namespace PermissionNext {
230231
const result = new Set<string>()
231232
for (const tool of tools) {
232233
const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool
233-
if (evaluate(permission, "*", ruleset) === "deny") {
234+
if (evaluate(permission, "*", ruleset).action === "deny") {
234235
result.add(tool)
235236
}
236237
}
237238
return result
238239
}
239240

240241
export class RejectedError extends Error {
241-
constructor(public readonly reason?: string) {
242+
constructor() {
243+
super(`The user rejected permission to use this specific tool call. You may try again with different parameters.`)
244+
}
245+
}
246+
247+
export class AutoRejectedError extends Error {
248+
constructor(public readonly ruleset: Ruleset) {
242249
super(
243-
reason !== undefined
244-
? reason
245-
: `The user rejected permission to use this specific tool call. You may try again with different parameters.`,
250+
`The user has specified a rule which prevents you from using this specific tool call. Here are some of the relevant rules ${JSON.stringify(ruleset)}`,
246251
)
247252
}
248253
}

0 commit comments

Comments
 (0)