From 1456db95028f90b75bb521ad036a6fcc9d7bdd45 Mon Sep 17 00:00:00 2001
From: ShayBC
Date: Sat, 8 Mar 2025 06:01:49 +0200
Subject: [PATCH 1/2] added approve finish task button and auto approve button
for subtasks
---
src/core/Cline.ts | 26 +++++++++++++++++--
src/shared/ExtensionMessage.ts | 3 +++
src/shared/WebviewMessage.ts | 1 +
.../src/components/chat/AutoApproveMenu.tsx | 16 ++++++++++++
webview-ui/src/components/chat/ChatRow.tsx | 12 +++++++++
webview-ui/src/components/chat/ChatView.tsx | 5 +++-
.../src/context/ExtensionStateContext.tsx | 2 ++
7 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/src/core/Cline.ts b/src/core/Cline.ts
index 51fb5265d41..c3e90d43009 100644
--- a/src/core/Cline.ts
+++ b/src/core/Cline.ts
@@ -1414,6 +1414,18 @@ export class Cline {
return true
}
+ const askFinishSubTaskApproval = async () => {
+ // ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished
+ // and return control to the parent task to continue running the rest of the sub-tasks
+ const toolMessage = JSON.stringify({
+ tool: "finishTask",
+ content:
+ "Task completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to continue with the next task.",
+ })
+
+ return await askApproval("tool", toolMessage)
+ }
+
const handleError = async (action: string, error: Error) => {
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
await this.say(
@@ -2941,8 +2953,13 @@ export class Cline {
if (lastMessage && lastMessage.ask !== "command") {
// havent sent a command message yet so first send completion_result then command
await this.say("completion_result", result, undefined, false)
- telemetryService.captureTaskCompleted(this.taskId)
+ // telemetryService.captureTaskCompleted(this.taskId)
if (this.isSubTask) {
+ const didApprove = await askFinishSubTaskApproval()
+ if (!didApprove) {
+ break
+ }
+
// tell the provider to remove the current subtask and resume the previous task in the stack
await this.providerRef
.deref()
@@ -2966,8 +2983,13 @@ export class Cline {
commandResult = execCommandResult
} else {
await this.say("completion_result", result, undefined, false)
- telemetryService.captureTaskCompleted(this.taskId)
+ // telemetryService.captureTaskCompleted(this.taskId)
if (this.isSubTask) {
+ const didApprove = await askFinishSubTaskApproval()
+ if (!didApprove) {
+ break
+ }
+
// tell the provider to remove the current subtask and resume the previous task in the stack
await this.providerRef
.deref()
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 98ff9b36e15..dcdaf017f36 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -109,6 +109,7 @@ export interface ExtensionState {
alwaysAllowMcp?: boolean
alwaysApproveResubmit?: boolean
alwaysAllowModeSwitch?: boolean
+ alwaysAllowFinishTask?: boolean
browserToolEnabled?: boolean
requestDelaySeconds: number
rateLimitSeconds: number // Minimum time between successive requests (0 = disabled)
@@ -168,6 +169,7 @@ export type ClineAsk =
| "mistake_limit_reached"
| "browser_action_launch"
| "use_mcp_server"
+ | "finishTask"
export type ClineSay =
| "task"
@@ -207,6 +209,7 @@ export interface ClineSayTool {
| "searchFiles"
| "switchMode"
| "newTask"
+ | "finishTask"
path?: string
diff?: string
content?: string
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index 10af6f7a946..086701a43fd 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -48,6 +48,7 @@ export interface WebviewMessage {
| "alwaysAllowBrowser"
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
+ | "alwaysAllowFinishTask"
| "playSound"
| "soundEnabled"
| "soundVolume"
diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx
index 161f3032b07..02f75cd28a7 100644
--- a/webview-ui/src/components/chat/AutoApproveMenu.tsx
+++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx
@@ -30,6 +30,8 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowMcp,
alwaysAllowModeSwitch,
setAlwaysAllowModeSwitch,
+ alwaysAllowFinishTask,
+ setAlwaysAllowFinishTask,
alwaysApproveResubmit,
setAlwaysApproveResubmit,
autoApprovalEnabled,
@@ -81,6 +83,13 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
description:
"Allows automatic switching between different AI modes and creating new tasks without requiring approval.",
},
+ {
+ id: "finishTask",
+ label: "Finish subtasks tasks",
+ shortName: "Finish",
+ enabled: alwaysAllowFinishTask ?? false,
+ description: "Allows automatic completeing a sub-task without requiring user review or approval.",
+ },
{
id: "retryRequests",
label: "Retry failed requests",
@@ -136,6 +145,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue })
}, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch])
+ const handleFinishTaskChange = useCallback(() => {
+ const newValue = !(alwaysAllowFinishTask ?? false)
+ setAlwaysAllowFinishTask(newValue)
+ vscode.postMessage({ type: "alwaysAllowFinishTask", bool: newValue })
+ }, [alwaysAllowFinishTask, setAlwaysAllowFinishTask])
+
const handleRetryChange = useCallback(() => {
const newValue = !(alwaysApproveResubmit ?? false)
setAlwaysApproveResubmit(newValue)
@@ -150,6 +165,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
useBrowser: handleBrowserChange,
useMcp: handleMcpChange,
switchModes: handleModeSwitchChange,
+ finishTask: handleFinishTaskChange,
retryRequests: handleRetryChange,
}
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index 1533bba3a8f..6f19df665e2 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -459,6 +459,18 @@ export const ChatRowContent = ({
>
)
+ case "finishTask":
+ return (
+ <>
+
+ {toolIcon("new-file")}
+ Roo wants to finish this task
+
+
+ {tool.content}
+
+ >
+ )
default:
return null
}
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 35e63dd3329..0f352819ef5 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -61,6 +61,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setMode,
autoApprovalEnabled,
alwaysAllowModeSwitch,
+ alwaysAllowFinishTask,
customModes,
telemetrySetting,
} = useExtensionState()
@@ -642,7 +643,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
(alwaysAllowModeSwitch &&
message.ask === "tool" &&
(JSON.parse(message.text || "{}")?.tool === "switchMode" ||
- JSON.parse(message.text || "{}")?.tool === "newTask"))
+ JSON.parse(message.text || "{}")?.tool === "newTask")) ||
+ (alwaysAllowFinishTask && message.ask === "finishTask")
)
},
[
@@ -657,6 +659,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
alwaysAllowMcp,
isMcpToolAlwaysAllowed,
alwaysAllowModeSwitch,
+ alwaysAllowFinishTask,
],
)
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index c4daf426ca1..aa132919c02 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -31,6 +31,7 @@ export interface ExtensionStateContextType extends ExtensionState {
setAlwaysAllowBrowser: (value: boolean) => void
setAlwaysAllowMcp: (value: boolean) => void
setAlwaysAllowModeSwitch: (value: boolean) => void
+ setAlwaysAllowFinishTask: (value: boolean) => void
setBrowserToolEnabled: (value: boolean) => void
setShowRooIgnoredFiles: (value: boolean) => void
setShowAnnouncement: (value: boolean) => void
@@ -247,6 +248,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })),
setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })),
+ setAlwaysAllowFinishTask: (value) => setState((prevState) => ({ ...prevState, alwaysAllowFinishTask: value })),
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
From 27624a25a54be1f124ab5f7e765e7cb0b62c97a0 Mon Sep 17 00:00:00 2001
From: ShayBC
Date: Sat, 8 Mar 2025 16:25:45 +0200
Subject: [PATCH 2/2] fixed the missing case of cmd execution at the end of a
subyask and added auto aprove option for finish task (and continue to the
next task)
---
src/core/Cline.ts | 33 ++++++-------------
src/core/webview/ClineProvider.ts | 7 ++++
src/shared/globalState.ts | 1 +
.../src/components/chat/AutoApproveMenu.tsx | 6 ++--
webview-ui/src/components/chat/ChatView.tsx | 8 ++++-
.../settings/AutoApproveSettings.tsx | 14 ++++++++
.../src/components/settings/SettingsView.tsx | 3 ++
7 files changed, 45 insertions(+), 27 deletions(-)
diff --git a/src/core/Cline.ts b/src/core/Cline.ts
index c3e90d43009..cd1ce7031d0 100644
--- a/src/core/Cline.ts
+++ b/src/core/Cline.ts
@@ -2953,19 +2953,7 @@ export class Cline {
if (lastMessage && lastMessage.ask !== "command") {
// havent sent a command message yet so first send completion_result then command
await this.say("completion_result", result, undefined, false)
- // telemetryService.captureTaskCompleted(this.taskId)
- if (this.isSubTask) {
- const didApprove = await askFinishSubTaskApproval()
- if (!didApprove) {
- break
- }
-
- // tell the provider to remove the current subtask and resume the previous task in the stack
- await this.providerRef
- .deref()
- ?.finishSubTask(`Task complete: ${lastMessage?.text}`)
- break
- }
+ telemetryService.captureTaskCompleted(this.taskId)
}
// complete command message
@@ -2983,19 +2971,18 @@ export class Cline {
commandResult = execCommandResult
} else {
await this.say("completion_result", result, undefined, false)
- // telemetryService.captureTaskCompleted(this.taskId)
- if (this.isSubTask) {
- const didApprove = await askFinishSubTaskApproval()
- if (!didApprove) {
- break
- }
+ telemetryService.captureTaskCompleted(this.taskId)
+ }
- // tell the provider to remove the current subtask and resume the previous task in the stack
- await this.providerRef
- .deref()
- ?.finishSubTask(`Task complete: ${lastMessage?.text}`)
+ if (this.isSubTask) {
+ const didApprove = await askFinishSubTaskApproval()
+ if (!didApprove) {
break
}
+
+ // tell the provider to remove the current subtask and resume the previous task in the stack
+ await this.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`)
+ break
}
// we already sent completion_result says, an empty string asks relinquishes control over button and field
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 70feb45c2fc..cc0b6f4f04a 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -984,6 +984,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("alwaysAllowModeSwitch", message.bool)
await this.postStateToWebview()
break
+ case "alwaysAllowFinishTask":
+ await this.updateGlobalState("alwaysAllowFinishTask", message.bool)
+ await this.postStateToWebview()
+ break
case "askResponse":
this.getCurrentCline()?.handleWebviewAskResponse(
message.askResponse!,
@@ -2177,6 +2181,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
+ alwaysAllowFinishTask,
soundEnabled,
diffEnabled,
enableCheckpoints,
@@ -2224,6 +2229,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowBrowser: alwaysAllowBrowser ?? false,
alwaysAllowMcp: alwaysAllowMcp ?? false,
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
+ alwaysAllowFinishTask: alwaysAllowFinishTask ?? false,
uriScheme: vscode.env.uriScheme,
currentTaskItem: this.getCurrentCline()?.taskId
? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
@@ -2385,6 +2391,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
+ alwaysAllowFinishTask: stateValues.alwaysAllowFinishTask ?? false,
taskHistory: stateValues.taskHistory,
allowedCommands: stateValues.allowedCommands,
soundEnabled: stateValues.soundEnabled ?? false,
diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts
index bfd24f42984..739fa11dad7 100644
--- a/src/shared/globalState.ts
+++ b/src/shared/globalState.ts
@@ -40,6 +40,7 @@ export const GLOBAL_STATE_KEYS = [
"alwaysAllowBrowser",
"alwaysAllowMcp",
"alwaysAllowModeSwitch",
+ "alwaysAllowFinishTask",
"taskHistory",
"openAiBaseUrl",
"openAiModelId",
diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx
index 02f75cd28a7..fba97f6c7d5 100644
--- a/webview-ui/src/components/chat/AutoApproveMenu.tsx
+++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx
@@ -85,10 +85,10 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
},
{
id: "finishTask",
- label: "Finish subtasks tasks",
- shortName: "Finish",
+ label: "Continue to next task",
+ shortName: "Continue",
enabled: alwaysAllowFinishTask ?? false,
- description: "Allows automatic completeing a sub-task without requiring user review or approval.",
+ description: "Allow tasks to end execution and continue to the next task, without user review or approval.",
},
{
id: "retryRequests",
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 0f352819ef5..b92604e157d 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -149,6 +149,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setPrimaryButtonText("Save")
setSecondaryButtonText("Reject")
break
+ case "finishTask":
+ setPrimaryButtonText("Approve & Continue to the next Task")
+ setSecondaryButtonText(undefined)
+ break
default:
setPrimaryButtonText("Approve")
setSecondaryButtonText("Reject")
@@ -644,7 +648,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
message.ask === "tool" &&
(JSON.parse(message.text || "{}")?.tool === "switchMode" ||
JSON.parse(message.text || "{}")?.tool === "newTask")) ||
- (alwaysAllowFinishTask && message.ask === "finishTask")
+ (alwaysAllowFinishTask &&
+ message.ask === "tool" &&
+ JSON.parse(message.text || "{}")?.tool === "finishTask")
)
},
[
diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx
index b2da2cab759..1c8e6c9ea91 100644
--- a/webview-ui/src/components/settings/AutoApproveSettings.tsx
+++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx
@@ -18,6 +18,7 @@ type AutoApproveSettingsProps = HTMLAttributes & {
requestDelaySeconds: number
alwaysAllowMcp?: boolean
alwaysAllowModeSwitch?: boolean
+ alwaysAllowFinishTask?: boolean
alwaysAllowExecute?: boolean
allowedCommands?: string[]
setCachedStateField: SetCachedStateField
@@ -32,6 +33,7 @@ export const AutoApproveSettings = ({
requestDelaySeconds,
alwaysAllowMcp,
alwaysAllowModeSwitch,
+ alwaysAllowFinishTask,
alwaysAllowExecute,
allowedCommands,
setCachedStateField,
@@ -180,6 +182,18 @@ export const AutoApproveSettings = ({
+
+
setCachedStateField("alwaysAllowFinishTask", e.target.checked)}>
+ Always approve finish & continue to next task
+
+
+ Automatically approve tasks to finish execution and continue to the next task, without user
+ review or approval
+
+
+
(({ onDone },
alwaysAllowExecute,
alwaysAllowMcp,
alwaysAllowModeSwitch,
+ alwaysAllowFinishTask,
alwaysAllowWrite,
alwaysApproveResubmit,
browserToolEnabled,
@@ -184,6 +185,7 @@ const SettingsView = forwardRef(({ onDone },
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
vscode.postMessage({ type: "updateExperimental", values: experiments })
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
+ vscode.postMessage({ type: "alwaysAllowFinishTask", bool: alwaysAllowFinishTask })
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
setChangeDetected(false)
@@ -364,6 +366,7 @@ const SettingsView = forwardRef(({ onDone },
requestDelaySeconds={requestDelaySeconds}
alwaysAllowMcp={alwaysAllowMcp}
alwaysAllowModeSwitch={alwaysAllowModeSwitch}
+ alwaysAllowFinishTask={alwaysAllowFinishTask}
alwaysAllowExecute={alwaysAllowExecute}
allowedCommands={allowedCommands}
setCachedStateField={setCachedStateField}