mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-31 06:54:35 +00:00
* feat: add experimental flag to disable command execution in attempt_completion tool * fix: remove deprecation phase comments from attemptCompletionTool * feat: add translations for disable completion command experiment * fix: revert unintended package.json change * Rename attempt-completion.experiment.test.ts to attempt-completion.test.ts * fix: address PR feedback - restore autoCondenseContext in tests and remove type assertion --------- Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com>
164 lines
5.5 KiB
TypeScript
164 lines
5.5 KiB
TypeScript
import Anthropic from "@anthropic-ai/sdk"
|
|
|
|
import { TelemetryService } from "@roo-code/telemetry"
|
|
|
|
import { Task } from "../task/Task"
|
|
import {
|
|
ToolResponse,
|
|
ToolUse,
|
|
AskApproval,
|
|
HandleError,
|
|
PushToolResult,
|
|
RemoveClosingTag,
|
|
ToolDescription,
|
|
AskFinishSubTaskApproval,
|
|
} from "../../shared/tools"
|
|
import { formatResponse } from "../prompts/responses"
|
|
import { type ExecuteCommandOptions, executeCommand } from "./executeCommandTool"
|
|
import { EXPERIMENT_IDS, experiments, experimentDefault } from "../../shared/experiments"
|
|
|
|
export async function attemptCompletionTool(
|
|
cline: Task,
|
|
block: ToolUse,
|
|
askApproval: AskApproval,
|
|
handleError: HandleError,
|
|
pushToolResult: PushToolResult,
|
|
removeClosingTag: RemoveClosingTag,
|
|
toolDescription: ToolDescription,
|
|
askFinishSubTaskApproval: AskFinishSubTaskApproval,
|
|
) {
|
|
const result: string | undefined = block.params.result
|
|
const command: string | undefined = block.params.command
|
|
|
|
try {
|
|
const lastMessage = cline.clineMessages.at(-1)
|
|
|
|
if (block.partial) {
|
|
if (command) {
|
|
// the attempt_completion text is done, now we're getting command
|
|
// remove the previous partial attempt_completion ask, replace with say, post state to webview, then stream command
|
|
|
|
// const secondLastMessage = cline.clineMessages.at(-2)
|
|
if (lastMessage && lastMessage.ask === "command") {
|
|
// update command
|
|
await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {})
|
|
} else {
|
|
// last message is completion_result
|
|
// we have command string, which means we have the result as well, so finish it (doesnt have to exist yet)
|
|
await cline.say("completion_result", removeClosingTag("result", result), undefined, false)
|
|
|
|
TelemetryService.instance.captureTaskCompleted(cline.taskId)
|
|
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
|
|
|
await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {})
|
|
}
|
|
} else {
|
|
// no command, still outputting partial result
|
|
await cline.say("completion_result", removeClosingTag("result", result), undefined, block.partial)
|
|
}
|
|
return
|
|
} else {
|
|
if (!result) {
|
|
cline.consecutiveMistakeCount++
|
|
cline.recordToolError("attempt_completion")
|
|
pushToolResult(await cline.sayAndCreateMissingParamError("attempt_completion", "result"))
|
|
return
|
|
}
|
|
|
|
cline.consecutiveMistakeCount = 0
|
|
|
|
let commandResult: ToolResponse | undefined
|
|
|
|
// Check if command execution is disabled via experiment
|
|
const state = await cline.providerRef.deref()?.getState()
|
|
const experimentsConfig = state?.experiments ?? experimentDefault
|
|
const isCommandDisabled = experiments.isEnabled(
|
|
experimentsConfig,
|
|
EXPERIMENT_IDS.DISABLE_COMPLETION_COMMAND,
|
|
)
|
|
|
|
if (command && !isCommandDisabled) {
|
|
if (lastMessage && lastMessage.ask !== "command") {
|
|
// Haven't sent a command message yet so first send completion_result then command.
|
|
await cline.say("completion_result", result, undefined, false)
|
|
TelemetryService.instance.captureTaskCompleted(cline.taskId)
|
|
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
|
}
|
|
|
|
// Complete command message.
|
|
const didApprove = await askApproval("command", command)
|
|
|
|
if (!didApprove) {
|
|
return
|
|
}
|
|
|
|
const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString()
|
|
const options: ExecuteCommandOptions = { executionId, command }
|
|
const [userRejected, execCommandResult] = await executeCommand(cline, options)
|
|
|
|
if (userRejected) {
|
|
cline.didRejectTool = true
|
|
pushToolResult(execCommandResult)
|
|
return
|
|
}
|
|
|
|
// User didn't reject, but the command may have output.
|
|
commandResult = execCommandResult
|
|
} else {
|
|
await cline.say("completion_result", result, undefined, false)
|
|
TelemetryService.instance.captureTaskCompleted(cline.taskId)
|
|
cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.toolUsage)
|
|
}
|
|
|
|
if (cline.parentTask) {
|
|
const didApprove = await askFinishSubTaskApproval()
|
|
|
|
if (!didApprove) {
|
|
return
|
|
}
|
|
|
|
// tell the provider to remove the current subtask and resume the previous task in the stack
|
|
await cline.providerRef.deref()?.finishSubTask(result)
|
|
return
|
|
}
|
|
|
|
// We already sent completion_result says, an
|
|
// empty string asks relinquishes control over
|
|
// button and field.
|
|
const { response, text, images } = await cline.ask("completion_result", "", false)
|
|
|
|
// Signals to recursive loop to stop (for now
|
|
// cline never happens since yesButtonClicked
|
|
// will trigger a new task).
|
|
if (response === "yesButtonClicked") {
|
|
pushToolResult("")
|
|
return
|
|
}
|
|
|
|
await cline.say("user_feedback", text ?? "", images)
|
|
const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = []
|
|
|
|
if (commandResult) {
|
|
if (typeof commandResult === "string") {
|
|
toolResults.push({ type: "text", text: commandResult })
|
|
} else if (Array.isArray(commandResult)) {
|
|
toolResults.push(...commandResult)
|
|
}
|
|
}
|
|
|
|
toolResults.push({
|
|
type: "text",
|
|
text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n<feedback>\n${text}\n</feedback>`,
|
|
})
|
|
|
|
toolResults.push(...formatResponse.imageBlocks(images))
|
|
cline.userMessageContent.push({ type: "text", text: `${toolDescription()} Result:` })
|
|
cline.userMessageContent.push(...toolResults)
|
|
|
|
return
|
|
}
|
|
} catch (error) {
|
|
await handleError("inspecting site", error)
|
|
return
|
|
}
|
|
}
|