-
Notifications
You must be signed in to change notification settings - Fork 270
[Fix] Task history disappears when user reopens a task #1319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0804572
597d067
2270d5a
254f4b1
f1f9b5f
5ac5e4a
79ba2a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,9 @@ | |
|
|
||
| import { describe, it, expect, vi, beforeEach } from "vitest" | ||
| import { RooCodeEventName } from "@roo-code/types" | ||
| import type { HistoryItem } from "@roo-code/types" | ||
| import type { ClineMessage, HistoryItem } from "@roo-code/types" | ||
|
|
||
| import type { ApiMessage } from "../core/task-persistence" | ||
|
|
||
| /* vscode mock for Task/Provider imports */ | ||
| vi.mock("vscode", () => { | ||
|
|
@@ -44,8 +46,8 @@ vi.mock("../core/task-persistence", async (importOriginal) => { | |
| return { | ||
| ...real, | ||
| readApiMessages: vi.fn().mockResolvedValue([]), | ||
| saveApiMessages: vi.fn().mockResolvedValue(undefined), | ||
| saveTaskMessages: vi.fn().mockResolvedValue(undefined), | ||
| saveApiMessages: vi.fn(async ({ messages }: { messages: unknown[] }) => messages), | ||
| saveTaskMessages: vi.fn(async ({ messages }: { messages: unknown[] }) => messages), | ||
| } | ||
| }) | ||
|
|
||
|
|
@@ -237,7 +239,7 @@ describe("History resume delegation - parent metadata transitions", () => { | |
| removeClineFromStack, | ||
| createTaskWithHistoryItem, | ||
| taskHistoryStore, | ||
| } as any) | ||
| } as unknown as ClineProvider) | ||
|
|
||
| vi.mocked(readTaskMessages).mockResolvedValue([]) | ||
| vi.mocked(readApiMessages).mockResolvedValue([]) | ||
|
|
@@ -347,13 +349,15 @@ describe("History resume delegation - parent metadata transitions", () => { | |
| expect.objectContaining({ | ||
| messages: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| messageId: expect.any(String), | ||
| type: "say", | ||
| say: "subtask_result", | ||
| text: "Subtask completed successfully", | ||
| }), | ||
| ]), | ||
| taskId: "p1", | ||
| globalStoragePath: "/storage", | ||
| merge: true, | ||
| }), | ||
| ) | ||
|
|
||
|
|
@@ -362,6 +366,7 @@ describe("History resume delegation - parent metadata transitions", () => { | |
| expect.objectContaining({ | ||
| messages: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| messageId: expect.any(String), | ||
| role: "user", | ||
| content: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
|
|
@@ -373,6 +378,7 @@ describe("History resume delegation - parent metadata transitions", () => { | |
| ]), | ||
| taskId: "p1", | ||
| globalStoragePath: "/storage", | ||
| merge: true, | ||
| }), | ||
| ) | ||
|
|
||
|
|
@@ -384,6 +390,105 @@ describe("History resume delegation - parent metadata transitions", () => { | |
| expect(apiCall.messages).toHaveLength(2) // 1 original + 1 injected | ||
| }) | ||
|
|
||
| it("hydrates the reopened parent from locked merge results without authoritative rewrites", async () => { | ||
| const parentItem = { | ||
| id: "parent-merge", | ||
| status: "delegated", | ||
| awaitingChildId: "child-merge", | ||
| childIds: ["child-merge"], | ||
| ts: 100, | ||
| task: "Parent", | ||
| tokensIn: 0, | ||
| tokensOut: 0, | ||
| totalCost: 0, | ||
| } | ||
| const overwriteClineMessages = vi.fn() | ||
| const overwriteApiConversationHistory = vi.fn() | ||
| const taskHistoryStore = makeTaskHistoryStoreStub({ id: "child-merge", status: "active" }, parentItem) | ||
| const provider = makeProviderStub({ | ||
| contextProxy: { globalStorageUri: { fsPath: "/storage" } }, | ||
| getTaskWithId: vi.fn().mockResolvedValue({ historyItem: parentItem }), | ||
| emit: vi.fn(), | ||
| getCurrentTask: vi.fn(() => ({ taskId: "child-merge" })), | ||
| removeClineFromStack: vi.fn().mockResolvedValue(undefined), | ||
| createTaskWithHistoryItem: vi.fn().mockResolvedValue({ | ||
| overwriteClineMessages, | ||
| overwriteApiConversationHistory, | ||
| resumeAfterDelegation: vi.fn().mockResolvedValue(undefined), | ||
| }), | ||
| taskHistoryStore, | ||
| } as any) | ||
|
|
||
| vi.mocked(readTaskMessages).mockResolvedValue([{ ts: 1, type: "say", say: "text", text: "initial UI" }]) | ||
| vi.mocked(readApiMessages).mockResolvedValue([{ ts: 1, role: "user", content: "initial API" }]) | ||
| vi.mocked(saveTaskMessages).mockResolvedValueOnce([ | ||
| { ts: 1, type: "say", say: "text", text: "initial UI" }, | ||
| { ts: 2, type: "say", say: "text", text: "concurrent UI" }, | ||
| ] satisfies ClineMessage[]) | ||
| vi.mocked(saveApiMessages).mockResolvedValueOnce([ | ||
| { ts: 1, role: "user", content: "initial API" }, | ||
| { ts: 2, role: "assistant", content: "concurrent API" }, | ||
| ] satisfies ApiMessage[]) | ||
|
Comment on lines
+424
to
+431
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Include the injected records in the mocked merged snapshots.
As per path instructions, require behavior-focused assertions for relevant regression paths. 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| await ClineProvider.prototype.reopenParentFromDelegation.call(provider, { | ||
| parentTaskId: "parent-merge", | ||
| childTaskId: "child-merge", | ||
| completionResultSummary: "Done", | ||
| }) | ||
|
|
||
| expect(overwriteClineMessages).toHaveBeenCalledWith( | ||
| [ | ||
| { ts: 1, type: "say", say: "text", text: "initial UI" }, | ||
| { ts: 2, type: "say", say: "text", text: "concurrent UI" }, | ||
| ], | ||
| false, | ||
| ) | ||
| expect(overwriteApiConversationHistory).toHaveBeenCalledWith( | ||
| [ | ||
| { ts: 1, role: "user", content: "initial API" }, | ||
| { ts: 2, role: "assistant", content: "concurrent API" }, | ||
| ], | ||
| false, | ||
| ) | ||
| }) | ||
|
|
||
| it("does not reopen or overwrite a parent when its UI history cannot be read", async () => { | ||
| const parentItem = { | ||
| id: "parent-read-failure", | ||
| status: "delegated", | ||
| awaitingChildId: "child-read-failure", | ||
| childIds: ["child-read-failure"], | ||
| ts: 100, | ||
| task: "Parent", | ||
| tokensIn: 0, | ||
| tokensOut: 0, | ||
| totalCost: 0, | ||
| } | ||
| const log = vi.fn() | ||
| const taskHistoryStore = makeTaskHistoryStoreStub({ id: "child-read-failure", status: "active" }, parentItem) | ||
| const provider = makeProviderStub({ | ||
| contextProxy: { globalStorageUri: { fsPath: "/storage" } }, | ||
| getTaskWithId: vi.fn().mockResolvedValue({ historyItem: parentItem }), | ||
| getCurrentTask: vi.fn(() => ({ taskId: "child-read-failure" })), | ||
| taskHistoryStore, | ||
| log, | ||
| }) | ||
| vi.mocked(readTaskMessages).mockRejectedValue(new Error("history unavailable")) | ||
|
|
||
| const result = await ClineProvider.prototype.reopenParentFromDelegation.call(provider, { | ||
| parentTaskId: "parent-read-failure", | ||
| childTaskId: "child-read-failure", | ||
| completionResultSummary: "Child done", | ||
| }) | ||
|
|
||
| expect(result).toBe(false) | ||
| expect(log).toHaveBeenCalledWith(expect.stringContaining("history unavailable")) | ||
| expect(readApiMessages).not.toHaveBeenCalled() | ||
| expect(saveTaskMessages).not.toHaveBeenCalled() | ||
| expect(saveApiMessages).not.toHaveBeenCalled() | ||
| expect(taskHistoryStore.atomicUpdatePair).not.toHaveBeenCalled() | ||
| }) | ||
|
|
||
| it("reopenParentFromDelegation injects tool_result when new_task tool_use exists in API history", async () => { | ||
| const parentItem = { | ||
| id: "p-tool", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove the new
anyassertion.makeProviderStubinfers the supplied object type and returnsClineProvider. Removeas anyso this test retains compile-time checks for its stub contract.As per path instructions, new code must introduce no
any.🤖 Prompt for AI Agents
Source: Path instructions