Skip to content

Commit b4c3fb4

Browse files
committed
Implement command denials
1 parent 4a26da4 commit b4c3fb4

File tree

2 files changed

+45
-32
lines changed

2 files changed

+45
-32
lines changed

src/core/auto-approval/index.ts

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ export type AutoApprovalStateOptions =
2929
| "allowedCommands" // For `alwaysAllowExecute`.
3030
| "deniedCommands"
3131

32-
export async function isAutoApproved({
32+
export type CheckAutoApprovalResult = "approve" | "deny" | "ask"
33+
34+
export async function checkAutoApproval({
3335
state,
3436
ask,
3537
text,
@@ -39,54 +41,63 @@ export async function isAutoApproved({
3941
ask: ClineAsk
4042
text?: string
4143
isProtected?: boolean
42-
}): Promise<boolean> {
44+
}): Promise<CheckAutoApprovalResult> {
4345
if (isNonBlockingAsk(ask)) {
44-
return true
46+
return "approve"
4547
}
4648

4749
if (!state.autoApprovalEnabled) {
48-
return false
50+
return "ask"
4951
}
5052

5153
// Note: The `alwaysApproveResubmit` check is already handled in `Task`.
5254

5355
if (ask === "followup") {
54-
return state.alwaysAllowFollowupQuestions === true
56+
return state.alwaysAllowFollowupQuestions === true ? "approve" : "ask"
5557
}
5658

5759
if (ask === "browser_action_launch") {
58-
return state.alwaysAllowBrowser === true
60+
return state.alwaysAllowBrowser === true ? "approve" : "ask"
5961
}
6062

6163
if (ask === "use_mcp_server") {
6264
if (!text) {
63-
return false
65+
return "ask"
6466
}
6567

6668
try {
6769
const mcpServerUse = JSON.parse(text) as McpServerUse
6870

6971
if (mcpServerUse.type === "use_mcp_tool") {
7072
return state.alwaysAllowMcp === true && isMcpToolAlwaysAllowed(mcpServerUse, state.mcpServers)
73+
? "approve"
74+
: "ask"
7175
} else if (mcpServerUse.type === "access_mcp_resource") {
72-
return state.alwaysAllowMcp === true
76+
return state.alwaysAllowMcp === true ? "approve" : "ask"
7377
}
7478
} catch (error) {
75-
return false
79+
return "ask"
7680
}
7781

78-
return false
82+
return "ask"
7983
}
8084

8185
if (ask === "command") {
8286
if (!text) {
83-
return false
87+
return "ask"
8488
}
8589

86-
return (
87-
state.alwaysAllowExecute === true &&
88-
getCommandDecision(text, state.allowedCommands || [], state.deniedCommands || []) === "auto_approve"
89-
)
90+
if (state.alwaysAllowExecute === true) {
91+
const decision = getCommandDecision(text, state.allowedCommands || [], state.deniedCommands || [])
92+
93+
if (decision === "auto_approve") {
94+
return "approve"
95+
} else if (decision === "auto_deny") {
96+
return "deny"
97+
} else {
98+
return "ask"
99+
}
100+
}
90101
}
91102

92103
if (ask === "tool") {
@@ -99,50 +110,50 @@ export async function isAutoApproved({
99110
}
100111

101112
if (!tool) {
102-
return false
113+
return "ask"
103114
}
104115

105116
if (tool.tool === "updateTodoList") {
106-
return state.alwaysAllowUpdateTodoList === true
117+
return state.alwaysAllowUpdateTodoList === true ? "approve" : "ask"
107118
}
108119

109120
if (tool?.tool === "fetchInstructions") {
110121
if (tool.content === "create_mode") {
111-
return state.alwaysAllowModeSwitch === true
122+
return state.alwaysAllowModeSwitch === true ? "approve" : "ask"
112123
}
113124

114125
if (tool.content === "create_mcp_server") {
115-
return state.alwaysAllowMcp === true
126+
return state.alwaysAllowMcp === true ? "approve" : "ask"
116127
}
117128
}
118129

119130
if (tool?.tool === "switchMode") {
120-
return state.alwaysAllowModeSwitch === true
131+
return state.alwaysAllowModeSwitch === true ? "approve" : "ask"
121132
}
122133

123134
if (["newTask", "finishTask"].includes(tool?.tool)) {
124-
return state.alwaysAllowSubtasks === true
135+
return state.alwaysAllowSubtasks === true ? "approve" : "ask"
125136
}
126137

127138
const isOutsideWorkspace = !!tool.isOutsideWorkspace
128139

129140
if (isReadOnlyToolAction(tool)) {
130-
return (
131-
state.alwaysAllowReadOnly === true &&
141+
return state.alwaysAllowReadOnly === true &&
132142
(!isOutsideWorkspace || state.alwaysAllowReadOnlyOutsideWorkspace === true)
133-
)
143+
? "approve"
144+
: "ask"
134145
}
135146

136147
if (isWriteToolAction(tool)) {
137-
return (
138-
state.alwaysAllowWrite === true &&
148+
return state.alwaysAllowWrite === true &&
139149
(!isOutsideWorkspace || state.alwaysAllowWriteOutsideWorkspace === true) &&
140150
(!isProtected || state.alwaysAllowWriteProtected === true)
141-
)
151+
? "approve"
152+
: "ask"
142153
}
143154
}
144155

145-
return false
156+
return "ask"
146157
}
147158

148159
export { AutoApprovalHandler } from "./AutoApprovalHandler"

src/core/task/Task.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ import { processUserContentMentions } from "../mentions/processUserContentMentio
116116
import { getMessagesSinceLastSummary, summarizeConversation } from "../condense"
117117
import { Gpt5Metadata, ClineMessageWithMetadata } from "./types"
118118
import { MessageQueueService } from "../message-queue/MessageQueueService"
119-
import { AutoApprovalHandler, isAutoApproved } from "../auto-approval"
119+
import { AutoApprovalHandler, checkAutoApproval } from "../auto-approval"
120120

121121
const MAX_EXPONENTIAL_BACKOFF_SECONDS = 600 // 10 minutes
122122
const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds
@@ -826,18 +826,20 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
826826
// Automatically approve if the ask according to the user's settings.
827827
const provider = this.providerRef.deref()
828828
const state = provider ? await provider.getState() : undefined
829-
const isApproved = state ? await isAutoApproved({ state, ask: type, text, isProtected }) : false
829+
const approval = state ? await checkAutoApproval({ state, ask: type, text, isProtected }) : false
830830

831-
if (isApproved) {
831+
if (approval === "approve") {
832832
this.approveAsk()
833+
} else if (approval === "deny") {
834+
this.denyAsk()
833835
}
834836

835837
// The state is mutable if the message is complete and the task will
836838
// block (via the `pWaitFor`).
837839
const isBlocking = !(this.askResponse !== undefined || this.lastMessageTs !== askTs)
838840
const isMessageQueued = !this.messageQueueService.isEmpty()
839841

840-
const isStatusMutable = !partial && isBlocking && !isMessageQueued && !isApproved
842+
const isStatusMutable = !partial && isBlocking && !isMessageQueued && approval === "ask"
841843
let statusMutationTimeouts: NodeJS.Timeout[] = []
842844
const statusMutationTimeout = 5_000
843845

0 commit comments

Comments
 (0)