Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5693b89
fix(write-to-file): capture a streaming failure once and roll it back…
easonLiangWorldedtech Nov 7, 2026
9bbb2ae
fix(tools): scope a refused write_to_file rollback to its target and …
easonLiangWorldedtech Nov 7, 2026
b7e3d81
test(assistant-message): give the abandoned-stream double the provide…
easonLiangWorldedtech Nov 7, 2026
6147c93
test(tools): compare the recovery record against the resolver that pr…
easonLiangWorldedtech Nov 7, 2026
ae4511f
fix(u5): keep one refused-rollback record per affected path
easonLiangWorldedtech Nov 7, 2026
214209e
fix(u5): re-acquire the diff editor before reporting a rollback recovery
easonLiangWorldedtech Nov 7, 2026
ccb1b5f
test(writeToFileTool): pin refused-rollback retention at the storage …
easonLiangWorldedtech Nov 8, 2026
21582eb
fix(u5): keep the task-wide refusal from deleting path-scoped ones
easonLiangWorldedtech Nov 8, 2026
7dd3077
fix(u5): roll the install back when the recovery cannot re-acquire th…
easonLiangWorldedtech Nov 8, 2026
7a1a710
test(tools): name the abort listener the cancelled-stream release mus…
easonLiangWorldedtech Nov 8, 2026
7bf8573
test(u5): hold the discard open to say which side of it the unlink si…
easonLiangWorldedtech Nov 8, 2026
22969c6
fix(u5): stop a timed-out acquisition from opening a diff nobody is w…
easonLiangWorldedtech Nov 8, 2026
23f42de
fix(u5): stop a recovery from rolling back an edit it no longer owns
Nov 8, 2026
13b1974
fix(editor): ask who owns the provider before the failed acquisition …
Nov 8, 2026
60f91cd
fix(u5): put the tab the abandoned pre-open left where the caller is not
Nov 8, 2026
e10d9d6
test(u5): name the interloper's editor and directories instead of an …
Nov 8, 2026
6702cc0
fix(diff-view): give a recovery the edit it was asked to undo, not ju…
Nov 8, 2026
8b95a8e
guard open() against finishing an install a later open() replaced
easonLiangWorldedtech Nov 8, 2026
b15f32d
allocate edit session numbers monotonically across a recovery
easonLiangWorldedtech Nov 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/__tests__/removeClineFromStack-delegation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ClineProvider } from "../core/webview/ClineProvider"
import { TaskRegistry } from "../core/task/TaskRegistry"
import { PendingActionSettlementError, type Task } from "../core/task/Task"
import { makeProviderStub } from "./helpers/provider-stub"
import { writeToFileTool } from "../core/tools/WriteToFileTool"

type PrivateClineProviderMethods = {
removeClineFromStack: (this: ClineProvider) => ReturnType<ClineProvider["removeClineFromStack"]>
Expand Down Expand Up @@ -202,6 +203,47 @@ describe("ClineProvider failed history restoration cleanup", () => {
expect(task.dispose).toHaveBeenCalledOnce()
})

it("releases the tool's per-task state before a directly disposed task loses its listeners", async () => {
const key = "failed-history-task.inst-1"
const order: string[] = []
const task = {
taskId: "failed-history-task",
instanceId: "inst-1",
emit: vi.fn(),
once: vi.fn(),
off: vi.fn(),
dispose: vi.fn().mockImplementation(() => {
// Dispose removes every listener, so this is the last moment the abort
// cleanup could still have run; the entry must already be gone here.
order.push(writeToFileTool["taskPartialStreamState"].has(key) ? "state-retained" : "state-cleared")
order.push("dispose")
return Promise.resolve()
}),
} as unknown as Task
// Fixture: the state entry the tool would have created during a partial stream.
writeToFileTool["taskPartialStreamState"].set(key, {
lastSeenPartialPath: undefined,
streamFailed: false,
streamError: undefined,
rollbackFailure: undefined,
task,
abortCleanup: () => {},
})
const taskEventListeners = new Map([[task, [vi.fn()]]])
const taskRegistry = new TaskRegistry()
taskRegistry.push(task)
const provider = { taskRegistry, taskEventListeners, log: vi.fn() } as unknown as ClineProvider

await privateClineProvider.cleanupFailedHistoryTask.call(
provider,
task,
new PendingActionSettlementError("settlement failed"),
)

expect(order).toEqual(["state-cleared", "dispose"])
expect(writeToFileTool["taskPartialStreamState"].size).toBe(0)
})

it("keeps the task active after an unrelated history resume failure", async () => {
const cleanupListener = vi.fn()
const task = {
Expand Down
Loading
Loading