From 16d716e7732c4a4ff3547368bc0cdfab62c0977a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Tue, 21 Apr 2026 00:46:29 +0800 Subject: [PATCH] fix: keep completed task lists from leaking into the next turn Completed desktop task bars were only being dismissed locally, which let the persisted task list resurface on refresh or bleed into the next user turn. This wires the existing server-side reset path into the desktop stores and session flow so a finished task cycle is summarized once, cleared locally, and removed remotely before the next round starts. Constraint: Existing task persistence already lives behind the server task-list API and must stay compatible with persisted JSON task files Rejected: Only hide the completed task bar in UI state | left stale persisted tasks behind and reintroduced them on reload Rejected: Clear desktop state without a task summary | dropped useful completion context from the chat transcript Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep desktop task dismissal and task-list persistence behavior aligned; do not reintroduce local-only clearing without covering reload and next-turn flows Tested: `cd desktop && bun run lint` Tested: `cd desktop && bun run test src/stores/cliTaskStore.test.ts src/stores/chatStore.test.ts src/components/chat/SessionTaskBar.test.tsx src/components/chat/ComputerUsePermissionModal.test.tsx` Tested: `bun test src/server/__tests__/e2e/business-flow.test.ts --test-name-pattern "Task Lists API"` Not-tested: Full `bun test src/server/__tests__/e2e/business-flow.test.ts` suite still has unrelated pre-existing failures in Models and Sessions sections --- desktop/src/api/cliTasks.ts | 5 ++ .../chat/ComputerUsePermissionModal.test.tsx | 1 + .../components/chat/SessionTaskBar.test.tsx | 16 ++++- .../src/components/chat/SessionTaskBar.tsx | 4 +- desktop/src/stores/chatStore.test.ts | 63 ++++++++++++++++++- desktop/src/stores/chatStore.ts | 10 ++- desktop/src/stores/cliTaskStore.test.ts | 39 ++++++++++++ desktop/src/stores/cliTaskStore.ts | 35 ++++++++++- .../__tests__/e2e/business-flow.test.ts | 58 +++++++++++++++++ src/server/api/agents.ts | 32 +++++++--- 10 files changed, 242 insertions(+), 21 deletions(-) diff --git a/desktop/src/api/cliTasks.ts b/desktop/src/api/cliTasks.ts index d659e91a..5e50993d 100644 --- a/desktop/src/api/cliTasks.ts +++ b/desktop/src/api/cliTasks.ts @@ -21,6 +21,11 @@ export const cliTasksApi = { return api.get(`/api/tasks/lists/${encodeURIComponent(taskListId)}/${taskId}`) }, + /** Clear all persisted tasks for a completed task list */ + resetTaskList(taskListId: string) { + return api.post<{ ok: true }>(`/api/tasks/lists/${encodeURIComponent(taskListId)}/reset`) + }, + /** List all tasks across all task lists */ listAll() { return api.get('/api/tasks') diff --git a/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx b/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx index c2bfce00..c8e372f2 100644 --- a/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +++ b/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx @@ -60,6 +60,7 @@ vi.mock('../../stores/cliTaskStore', () => ({ clearTasks: vi.fn(), setTasksFromTodos: vi.fn(), markCompletedAndDismissed: vi.fn(), + resetCompletedTasks: vi.fn(async () => {}), refreshTasks: vi.fn(), }), }, diff --git a/desktop/src/components/chat/SessionTaskBar.test.tsx b/desktop/src/components/chat/SessionTaskBar.test.tsx index 3b7f6bf7..b5607f31 100644 --- a/desktop/src/components/chat/SessionTaskBar.test.tsx +++ b/desktop/src/components/chat/SessionTaskBar.test.tsx @@ -4,6 +4,13 @@ import '@testing-library/jest-dom' import { SessionTaskBar } from './SessionTaskBar' import { useCLITaskStore } from '../../stores/cliTaskStore' +vi.mock('../../api/cliTasks', () => ({ + cliTasksApi: { + getTasksForList: vi.fn(), + resetTaskList: vi.fn(async () => ({ ok: true })), + }, +})) + vi.mock('../../i18n', () => ({ useTranslation: () => (key: string) => { const translations: Record = { @@ -46,7 +53,7 @@ describe('SessionTaskBar', () => { expect(screen.queryByRole('button', { name: 'Hide completed tasks' })).toBeNull() }) - it('hides the bar after dismissing a completed task set', () => { + it('hides the bar after dismissing a completed task set', async () => { act(() => { useCLITaskStore.getState().setTasksFromTodos([ { content: 'first', status: 'completed' }, @@ -58,10 +65,13 @@ describe('SessionTaskBar', () => { render() }) - fireEvent.click(screen.getByRole('button', { name: 'Hide completed tasks' })) + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: 'Hide completed tasks' })) + await Promise.resolve() + }) expect(screen.queryByText('Tasks')).toBeNull() - expect(useCLITaskStore.getState().completedAndDismissed).toBe(true) + expect(useCLITaskStore.getState().tasks).toEqual([]) }) it('shows the bar again for a new task cycle after a previous completed set was dismissed', () => { diff --git a/desktop/src/components/chat/SessionTaskBar.tsx b/desktop/src/components/chat/SessionTaskBar.tsx index 2bc9b05d..07af20f9 100644 --- a/desktop/src/components/chat/SessionTaskBar.tsx +++ b/desktop/src/components/chat/SessionTaskBar.tsx @@ -26,7 +26,7 @@ export function SessionTaskBar() { expanded, toggleExpanded, completedAndDismissed, - markCompletedAndDismissed, + resetCompletedTasks, } = useCLITaskStore() const t = useTranslation() @@ -91,7 +91,7 @@ export function SessionTaskBar() {