From ce2fee926f2934810be55735964ea064bd8c1a7b Mon Sep 17 00:00:00 2001 From: ytahdn <1294726970@qq.com> Date: Tue, 7 Jul 2026 16:59:53 +0800 Subject: [PATCH] fix(web-shell): clear stale floating todos (#6425) * fix(web-shell): clear stale floating todos * test(web-shell): cover floating todo reset --------- Co-authored-by: ytahdn --- packages/web-shell/client/utils/todos.test.ts | 28 +++++++++++++++++-- packages/web-shell/client/utils/todos.ts | 6 ++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/web-shell/client/utils/todos.test.ts b/packages/web-shell/client/utils/todos.test.ts index b1b07be573..36f9e2b4ad 100644 --- a/packages/web-shell/client/utils/todos.test.ts +++ b/packages/web-shell/client/utils/todos.test.ts @@ -75,6 +75,10 @@ function userMessage(id: string): Message { return { id, role: 'user', content: 'hello' }; } +function userShellMessage(id: string): Message { + return { id, role: 'user_shell', command: 'npm test', output: '' }; +} + function assistantMessage(id: string): Message { return { id, role: 'assistant', content: 'working on it' }; } @@ -110,12 +114,21 @@ describe('getFloatingTodos', () => { expect(state.sourceCallId).toBeNull(); }); - it('keeps an active list visible across later user messages', () => { + it('clears an active list after the next user message', () => { const state = getFloatingTodos([ todoWriteMessage('m1', [todo('1', 'in_progress')]), userMessage('u1'), ]); - expect(state.todos).toHaveLength(1); + expect(state.todos).toHaveLength(0); + expect(state.allCompleted).toBe(false); + }); + + it('clears an active list after the next user shell message', () => { + const state = getFloatingTodos([ + todoWriteMessage('m1', [todo('1', 'in_progress')]), + userShellMessage('shell-1'), + ]); + expect(state.todos).toHaveLength(0); expect(state.allCompleted).toBe(false); }); @@ -145,6 +158,17 @@ describe('getFloatingTodos', () => { expect(state.sourceMessageId).toBe('m2'); }); + it('shows a new active list started after clearing a stale active one', () => { + const state = getFloatingTodos([ + todoWriteMessage('m1', [todo('1', 'in_progress')]), + userMessage('u1'), + todoWriteMessage('m2', [todo('2', 'pending')]), + ]); + expect(state.todos.map((t) => t.id)).toEqual(['2']); + expect(state.allCompleted).toBe(false); + expect(state.sourceMessageId).toBe('m2'); + }); + it('ignores user messages sent before the todo update', () => { const state = getFloatingTodos([ userMessage('u1'), diff --git a/packages/web-shell/client/utils/todos.ts b/packages/web-shell/client/utils/todos.ts index bbd37e5f2b..8c2c072416 100644 --- a/packages/web-shell/client/utils/todos.ts +++ b/packages/web-shell/client/utils/todos.ts @@ -96,7 +96,7 @@ export function getFloatingTodos( let userMessageAfter = false; for (const message of messages) { - if (message.role === 'user') { + if (message.role === 'user' || message.role === 'user_shell') { userMessageAfter = true; continue; } @@ -122,9 +122,7 @@ export function getFloatingTodos( if (todos.length === 0) return EMPTY_FLOATING_TODOS; const allCompleted = !hasActiveTodos(todos); - // A finished list stays visible (the "all done" moment) only until the - // user sends the next prompt. - if (allCompleted && userMessageAfter) return EMPTY_FLOATING_TODOS; + if (userMessageAfter) return EMPTY_FLOATING_TODOS; return { todos, allCompleted, sourceMessageId, sourceCallId }; }