Compare commits

...

1 Commits

Author SHA1 Message Date
Robin Newhouse d9ab1c34ad fix(task): accept response alias for attempt_completion result
Handle provider variance where attempt_completion sometimes emits `response` instead of `result`, and add regression coverage so we don't reintroduce missing-parameter failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 21:02:19 -06:00
3 changed files with 82 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Accept `response` as an alias for `attempt_completion`'s `result` parameter to handle provider output variance and prevent false missing-parameter errors.
@@ -19,6 +19,10 @@ import { ToolResultUtils } from "../utils/ToolResultUtils"
const TASK_PREVIEW_MAX_CHARS = 8000
function getCompletionResult(params: ToolUse["params"]): string | undefined {
return params.result || params.response
}
function getInitialTaskPreview(config: TaskConfig): string | undefined {
const firstTaskMessage = config.messageState
.getClineMessages()
@@ -44,7 +48,8 @@ export class AttemptCompletionHandler implements IToolHandler, IPartialBlockHand
* Handle partial block streaming for attempt_completion
*/
async handlePartialBlock(block: ToolUse, uiHelpers: StronglyTypedUIHelpers): Promise<void> {
const result = uiHelpers.removeClosingTag(block, "result", block.params.result)
const rawResult = getCompletionResult(block.params)
const result = uiHelpers.removeClosingTag(block, "result", rawResult)
if (result) {
await uiHelpers.say("completion_result", result, undefined, undefined, block.partial)
}
@@ -52,7 +57,7 @@ export class AttemptCompletionHandler implements IToolHandler, IPartialBlockHand
}
async execute(config: TaskConfig, block: ToolUse): Promise<ToolResponse> {
const result: string | undefined = block.params.result
const result: string | undefined = getCompletionResult(block.params)
const command: string | undefined = block.params.command
// Validate required parameters
@@ -315,7 +320,7 @@ export class AttemptCompletionHandler implements IToolHandler, IPartialBlockHand
taskMetadata: {
taskId: config.taskId,
ulid: config.ulid,
result: block.params.result || "",
result: getCompletionResult(block.params) || "",
command: block.params.command || "",
},
},
@@ -0,0 +1,69 @@
import { strict as assert } from "node:assert"
import { ClineDefaultTool } from "@shared/tools"
import { describe, it } from "mocha"
import sinon from "sinon"
import { TaskState } from "../../../TaskState"
import type { TaskConfig } from "../../types/TaskConfig"
import { AttemptCompletionHandler } from "../AttemptCompletionHandler"
function createConfig(options?: { doubleCheckEnabled?: boolean; pending?: boolean }) {
const taskState = new TaskState()
taskState.doubleCheckCompletionPending = options?.pending ?? false
taskState.consecutiveMistakeCount = 2
const callbacks = {
sayAndCreateMissingParamError: sinon.stub().resolves("missing-param"),
removeLastPartialMessageIfExistsWithType: sinon.stub().resolves(),
} as unknown as TaskConfig["callbacks"]
const config = {
taskId: "task-1",
ulid: "ulid-1",
taskState,
doubleCheckCompletionEnabled: options?.doubleCheckEnabled ?? false,
messageState: {
getClineMessages: () => [],
},
callbacks,
} as unknown as TaskConfig
return { config, callbacks, taskState }
}
describe("AttemptCompletionHandler result alias", () => {
it("accepts response as result for validation", async () => {
const { config, callbacks, taskState } = createConfig({ doubleCheckEnabled: true, pending: false })
const handler = new AttemptCompletionHandler()
const result = await handler.execute(config, {
type: "tool_use",
name: ClineDefaultTool.ATTEMPT,
params: {
response: "done via response alias",
},
partial: false,
})
assert.equal(taskState.consecutiveMistakeCount, 0)
assert.equal(taskState.doubleCheckCompletionPending, true)
sinon.assert.notCalled(callbacks.sayAndCreateMissingParamError as sinon.SinonStub)
assert.equal(typeof result, "string")
assert.ok((result as string).includes("Before completing"))
})
it("still errors when both result and response are missing", async () => {
const { config, callbacks, taskState } = createConfig()
const handler = new AttemptCompletionHandler()
const result = await handler.execute(config, {
type: "tool_use",
name: ClineDefaultTool.ATTEMPT,
params: {},
partial: false,
})
assert.equal(result, "missing-param")
assert.equal(taskState.consecutiveMistakeCount, 3)
sinon.assert.calledOnce(callbacks.sayAndCreateMissingParamError as sinon.SinonStub)
})
})