Merge pull request #1491 from shaybc/sbc_subtasks_approve_btn

Sbc subtasks approve button
This commit is contained in:
Matt Rubens 2025-03-08 21:16:44 -05:00 committed by GitHub
commit e945dcf94f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 90 additions and 13 deletions

View file

@ -1417,6 +1417,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(
@ -2945,13 +2957,6 @@ export class Cline {
// 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) {
// 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
}
}
// complete command message
@ -2970,13 +2975,17 @@ export class Cline {
} else {
await this.say("completion_result", result, undefined, false)
telemetryService.captureTaskCompleted(this.taskId)
if (this.isSubTask) {
// 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

View file

@ -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,

View file

@ -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

View file

@ -48,6 +48,7 @@ export interface WebviewMessage {
| "alwaysAllowBrowser"
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
| "alwaysAllowFinishTask"
| "playSound"
| "soundEnabled"
| "soundVolume"

View file

@ -40,6 +40,7 @@ export const GLOBAL_STATE_KEYS = [
"alwaysAllowBrowser",
"alwaysAllowMcp",
"alwaysAllowModeSwitch",
"alwaysAllowFinishTask",
"taskHistory",
"openAiBaseUrl",
"openAiModelId",

View file

@ -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: "Continue to next task",
shortName: "Continue",
enabled: alwaysAllowFinishTask ?? false,
description: "Allow tasks to end execution and continue to the next task, without 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,
}

View file

@ -459,6 +459,18 @@ export const ChatRowContent = ({
</div>
</>
)
case "finishTask":
return (
<>
<div style={headerStyle}>
{toolIcon("new-file")}
<span style={{ fontWeight: "bold" }}>Roo wants to finish this task</span>
</div>
<div style={{ paddingLeft: "26px", marginTop: "4px" }}>
<code>{tool.content}</code>
</div>
</>
)
default:
return null
}

View file

@ -61,6 +61,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setMode,
autoApprovalEnabled,
alwaysAllowModeSwitch,
alwaysAllowFinishTask,
customModes,
telemetrySetting,
} = useExtensionState()
@ -148,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")
@ -642,7 +647,10 @@ 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 === "tool" &&
JSON.parse(message.text || "{}")?.tool === "finishTask")
)
},
[
@ -657,6 +665,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
alwaysAllowMcp,
isMcpToolAlwaysAllowed,
alwaysAllowModeSwitch,
alwaysAllowFinishTask,
],
)

View file

@ -18,6 +18,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
requestDelaySeconds: number
alwaysAllowMcp?: boolean
alwaysAllowModeSwitch?: boolean
alwaysAllowFinishTask?: boolean
alwaysAllowExecute?: boolean
allowedCommands?: string[]
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
@ -32,6 +33,7 @@ export const AutoApproveSettings = ({
requestDelaySeconds,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowFinishTask,
alwaysAllowExecute,
allowedCommands,
setCachedStateField,
@ -180,6 +182,18 @@ export const AutoApproveSettings = ({
</p>
</div>
<div>
<VSCodeCheckbox
checked={alwaysAllowFinishTask}
onChange={(e: any) => setCachedStateField("alwaysAllowFinishTask", e.target.checked)}>
<span className="font-medium">Always approve finish & continue to next task</span>
</VSCodeCheckbox>
<p className="text-vscode-descriptionForeground text-sm mt-0">
Automatically approve tasks to finish execution and continue to the next task, without user
review or approval
</p>
</div>
<div>
<VSCodeCheckbox
checked={alwaysAllowExecute}

View file

@ -63,6 +63,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
alwaysAllowExecute,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowFinishTask,
alwaysAllowWrite,
alwaysApproveResubmit,
browserToolEnabled,
@ -184,6 +185,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ 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<SettingsViewRef, SettingsViewProps>(({ onDone },
requestDelaySeconds={requestDelaySeconds}
alwaysAllowMcp={alwaysAllowMcp}
alwaysAllowModeSwitch={alwaysAllowModeSwitch}
alwaysAllowFinishTask={alwaysAllowFinishTask}
alwaysAllowExecute={alwaysAllowExecute}
allowedCommands={allowedCommands}
setCachedStateField={setCachedStateField}

View file

@ -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 })),