Skip to content

Commit 5223a10

Browse files
author
easonLiangWorldedtech
committed
fix(task): stage-independent saveClineMessages + finalize open partial tool ask
1 parent 7214352 commit 5223a10

3 files changed

Lines changed: 476 additions & 5 deletions

File tree

‎src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ describe("presentAssistantMessage - Custom Tool Recording", () => {
8989
},
9090
say: vi.fn().mockResolvedValue(undefined),
9191
ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked" }),
92+
finalizePartialToolAsk: vi.fn().mockResolvedValue(undefined),
9293
}
9394

9495
// Add pushToolResultToUserContent method after mockTask is created so it can reference mockTask

‎src/core/task/Task.ts‎

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
6868
import { OutputTokenLimitError } from "../../api/providers/utils/output-token-limit-error"
6969

7070
// shared
71-
import { findLastIndex } from "../../shared/array"
71+
import { findLast, findLastIndex } from "../../shared/array"
7272
import { combineApiRequests } from "../../shared/combineApiRequests"
7373
import { combineCommandSequences } from "../../shared/combineCommandSequences"
7474
import { t } from "../../i18n"
@@ -1678,7 +1678,19 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
16781678
}
16791679
}
16801680

1681-
/** Persists Cline messages and updates task metadata in the history store. Returns false on failure. */
1681+
/**
1682+
* Persist the message array, then refresh the derived metadata / task-history entries.
1683+
*
1684+
* The returned boolean reflects the message write only: `saveTaskMessages` failure
1685+
* leaves the on-disk record stale, so callers gating UI updates on durable state must
1686+
* skip them. Metadata / task-history stage failures are logged and swallowed — the
1687+
* message array is already persisted, and the next save recomputes and re-emits the
1688+
* metadata.
1689+
*
1690+
* `merge` (default `true`) is passed through to `saveTaskMessages`: the in-memory
1691+
* snapshot is merged with the on-disk record. `overwriteClineMessages` passes `false`
1692+
* to replace the stored messages outright.
1693+
*/
16821694
private async saveClineMessages(merge = true): Promise<boolean> {
16831695
try {
16841696
await saveTaskMessages({
@@ -1687,7 +1699,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
16871699
globalStoragePath: this.globalStoragePath,
16881700
merge,
16891701
})
1702+
} catch (error) {
1703+
console.error("Failed to save Roo messages:", error)
1704+
return false
1705+
}
16901706

1707+
try {
16911708
if (this._taskApiConfigName === undefined) {
16921709
await this.taskApiConfigReady
16931710
}
@@ -1715,11 +1732,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17151732
const provider = this.providerRef.deref()
17161733
const existingStatus = provider?.taskHistoryStore.get(this.taskId)?.status
17171734
await provider?.updateTaskHistory(existingStatus ? { ...historyItem, status: existingStatus } : historyItem)
1718-
return true
17191735
} catch (error) {
1720-
console.error("Failed to save Roo messages:", error)
1721-
return false
1736+
// The message array was persisted above; a metadata or task-history failure must
1737+
// not mask that write (see the method docs). The next saveClineMessages() call
1738+
// recomputes and re-emits the metadata update.
1739+
console.error("Failed to save task metadata:", error)
17221740
}
1741+
1742+
return true
17231743
}
17241744

17251745
private findMessageByTimestamp(ts: number): ClineMessage | undefined {
@@ -2712,6 +2732,56 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27122732
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName))
27132733
}
27142734

2735+
/**
2736+
* Finalize a partial "tool" ask message without blocking for user input.
2737+
* Call this in error paths where a partial tool message was opened during streaming
2738+
* but execution failed before the normal approval flow could close it, so the webview
2739+
* spinner does not get stuck in a loading state.
2740+
*
2741+
* The matching partial message may no longer be the final entry if another asynchronous
2742+
* message was inserted between the partial ask and the error handler, so search backward
2743+
* instead of relying on clineMessages.at(-1).
2744+
*
2745+
* Any in-progress `progressStatus` on the message is cleared as well: the ask is being
2746+
* finalized because it will NOT complete, so a stale "in progress" indicator would be
2747+
* misleading (the normal completion path overwrites it with the final status instead).
2748+
*
2749+
* `isAnswered` is stamped true because the ask is resolved by the system rather than
2750+
* by the user: ChatView only shows ask buttons for unanswered messages, so leaving it
2751+
* unset would keep Save/Reject armed for a write that already failed.
2752+
*/
2753+
async finalizePartialToolAsk(text?: string): Promise<void> {
2754+
const partialToolAsk = findLast(
2755+
this.clineMessages,
2756+
(message) =>
2757+
message.partial === true &&
2758+
message.type === "ask" &&
2759+
message.ask === "tool" &&
2760+
(text === undefined || message.text === text),
2761+
)
2762+
2763+
if (!partialToolAsk) {
2764+
return
2765+
}
2766+
2767+
partialToolAsk.partial = false
2768+
partialToolAsk.progressStatus = undefined
2769+
partialToolAsk.isAnswered = true
2770+
const saved = await this.saveClineMessages()
2771+
if (!saved) {
2772+
// The persistence write failed: the on-disk record still carries `partial: true`
2773+
// while the in-memory message is finalized. Skip the webview-only update so the
2774+
// two views do not diverge (a later state resync or restart reload would flip the
2775+
// spinner back on from the stale disk record). The next saveClineMessages() call
2776+
// re-persists the full message array and repairs the disk record.
2777+
console.error("[Task#finalizePartialToolAsk] saveClineMessages failed; skipping webview update")
2778+
return
2779+
}
2780+
await this.updateClineMessage(partialToolAsk).catch((error) => {
2781+
console.error("[Task#finalizePartialToolAsk] updateClineMessage failed:", error)
2782+
})
2783+
}
2784+
27152785
// Lifecycle
27162786
// Start / Resume / Abort / Dispose
27172787

0 commit comments

Comments
 (0)