From ee353b63eebe0f5e438b3bbbdf3c56ae616aac07 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Sat, 8 Nov 2025 18:42:19 +0300 Subject: [PATCH 01/10] fix: isolate new chat state from current replaying chat --- src/components/ChatBox/index.tsx | 7 +++++-- src/store/projectStore.ts | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/ChatBox/index.tsx b/src/components/ChatBox/index.tsx index 7e3f8af3f..9da848c99 100644 --- a/src/components/ChatBox/index.tsx +++ b/src/components/ChatBox/index.tsx @@ -102,7 +102,6 @@ export default function ChatBox(): JSX.Element { const task = chatStore.tasks[_taskId]; const isTaskBusy = ( // running or paused counts as busy - // TODO: Bug where when replay end hasMessages = false & status = running (task.status === 'running' && !task.hasMessages) || task.status === 'pause' || // splitting phase: has to_sub_tasks not confirmed OR skeleton computing task.messages.some(m => m.step === 'to_sub_tasks' && !m.isConfirm) || @@ -111,6 +110,10 @@ export default function ChatBox(): JSX.Element { (!!task.messages.find(m => m.step === 'to_sub_tasks' && !m.isConfirm) && task.status === 'pending') ); + //Fixes bug where doesn't matter the SSE order or final state of the chatStore + const isReplayChatStore = chatStore.tasks[_taskId as string]?.type === "replay"; + const queueTask = isTaskBusy && !isReplayChatStore; + console.log(`Current task is ${isTaskBusy} with ${task}`); if (textareaRef.current) textareaRef.current.style.height = "60px"; @@ -152,7 +155,7 @@ export default function ChatBox(): JSX.Element { } } else { // If current task is busy (splitting/confirm/running), queue the new message instead of sending immediately - if (isTaskBusy) { + if (queueTask) { const project_id = projectStore.activeProjectId; // Queue the message locally; do not send to backend yet. const currentAttaches = JSON.parse(JSON.stringify(task.attaches)) || []; diff --git a/src/store/projectStore.ts b/src/store/projectStore.ts index 40cdcf4dc..6fcdb22ab 100644 --- a/src/store/projectStore.ts +++ b/src/store/projectStore.ts @@ -196,7 +196,8 @@ const projectStore = create()((set, get) => ({ queuedMessages: [], // Initialize empty queued messages array metadata: { status: 'active', - historyId: historyId + historyId: historyId, + tags: type === ProjectType.REPLAY ? ["replay"] : [] } }; From 0cff90c3496b08a16481aef8f6f0cd5a92df9f14 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Sat, 8 Nov 2025 18:52:17 +0300 Subject: [PATCH 02/10] fix: first prompt on click replay --- src/lib/replay.ts | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/src/lib/replay.ts b/src/lib/replay.ts index 6f0b1ddb4..6e935172b 100644 --- a/src/lib/replay.ts +++ b/src/lib/replay.ts @@ -57,7 +57,8 @@ export const replayActiveTask = async ( if (project && project.chatStores) { Object.entries(project.chatStores).forEach(([chatStoreId, chatStoreData]: [string, any]) => { const timestamp = project.chatStoreTimestamps[chatStoreId] || 0; - + chatStoreData = chatStoreData.getState(); + if (chatStoreData.tasks) { Object.values(chatStoreData.tasks).forEach((task: any) => { // Check messages for user content @@ -68,33 +69,6 @@ export const replayActiveTask = async ( earliestTimestamp = timestamp; } } - - // Check task info for original questions - if (task.taskInfo && task.taskInfo.length > 0) { - task.taskInfo.forEach((info: any) => { - if (info.content && timestamp < earliestTimestamp) { - question = info.content; - earliestTimestamp = timestamp; - } - }); - } - - // Check agent logs for original task content - if (task.taskAssigning) { - task.taskAssigning.forEach((agent: any) => { - if (agent.log) { - agent.log.forEach((logEntry: any) => { - if (logEntry.data && logEntry.data.message) { - const match = logEntry.data.message.match(/==============================\n(.*?)\n==============================/s); - if (match && match[1] && timestamp < earliestTimestamp) { - question = match[1].trim(); - earliestTimestamp = timestamp; - } - } - }); - } - }); - } }); } }); @@ -103,6 +77,7 @@ export const replayActiveTask = async ( // Fallback to current task's first message if no question found if (!question && chatStore.tasks[taskId] && chatStore.tasks[taskId].messages[0]) { question = chatStore.tasks[taskId].messages[0].content; + console.log("[REPLAY] question fall back to ", question); } const historyId = projectStore.getHistoryId(projectId); From 6e3194301f62d2138d79e4deda17da45baaed55d Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Sat, 8 Nov 2025 19:28:05 +0300 Subject: [PATCH 03/10] enhance: update types --- src/lib/replay.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/replay.ts b/src/lib/replay.ts index 6e935172b..f5278eb6e 100644 --- a/src/lib/replay.ts +++ b/src/lib/replay.ts @@ -1,3 +1,5 @@ +import { ChatStore } from "@/store/chatStore"; +import { ProjectStore } from "@/store/projectStore"; import { NavigateFunction } from "react-router-dom"; /** @@ -11,7 +13,7 @@ import { NavigateFunction } from "react-router-dom"; * @param historyId - The history ID for the replay */ export const replayProject = async ( - projectStore: any, + projectStore: ProjectStore, navigate: NavigateFunction, projectId: string, question: string, @@ -36,8 +38,8 @@ export const replayProject = async ( * @param navigate - The navigate function from useNavigate hook */ export const replayActiveTask = async ( - chatStore: any, - projectStore: any, + chatStore: ChatStore, + projectStore: ProjectStore, navigate: NavigateFunction ) => { const taskId = chatStore.activeTaskId as string; From e27f9e3068a6e9651e160e5c21f13705c7afd062 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Sat, 8 Nov 2025 19:34:59 +0300 Subject: [PATCH 04/10] feat(test): replicate issue 619 sse and test replayActiveTask --- .../chatStore/activeQueue.test.tsx | 21 +- .../chatStore/replayComplete.test.tsx | 323 ++++++++++++++++-- test/mocks/sse.mock.ts | 321 ++++++++++++++++- 3 files changed, 623 insertions(+), 42 deletions(-) diff --git a/test/integration/chatStore/activeQueue.test.tsx b/test/integration/chatStore/activeQueue.test.tsx index 470024883..7c7527bbb 100644 --- a/test/integration/chatStore/activeQueue.test.tsx +++ b/test/integration/chatStore/activeQueue.test.tsx @@ -13,26 +13,7 @@ import '../../../src/store/chatStore' import { useProjectStore } from '../../../src/store/projectStore' import useChatStoreAdapter from '../../../src/hooks/useChatStoreAdapter' -import { mockFetchEventSource } from '../../mocks/sse.mock' - -// Helper function for sequential SSE events -const createSSESequence = (events: Array<{ event: any; delay: number }>) => { - return async (onMessage: (data: any) => void) => { - for (let i = 0; i < events.length; i++) { - const { event, delay } = events[i] - - await new Promise((resolve) => { - setTimeout(() => { - console.log(`Sending SSE Event ${i + 1}:`, event.step); - onMessage({ - data: JSON.stringify(event) - }) - resolve() - }, delay) - }) - } - } -} +import { mockFetchEventSource, createSSESequence } from '../../mocks/sse.mock' // Mock electron IPC (global as any).ipcRenderer = { diff --git a/test/integration/chatStore/replayComplete.test.tsx b/test/integration/chatStore/replayComplete.test.tsx index a9dd0c162..dc877fe7a 100644 --- a/test/integration/chatStore/replayComplete.test.tsx +++ b/test/integration/chatStore/replayComplete.test.tsx @@ -13,27 +13,8 @@ import '../../../src/store/chatStore' import { useProjectStore } from '../../../src/store/projectStore' import useChatStoreAdapter from '../../../src/hooks/useChatStoreAdapter' -import { mockFetchEventSource } from '../../mocks/sse.mock' -import { replayProject } from '../../../src/lib' - -// Helper function for sequential SSE events -const createSSESequence = (events: Array<{ event: any; delay: number }>) => { - return async (onMessage: (data: any) => void) => { - for (let i = 0; i < events.length; i++) { - const { event, delay } = events[i] - - await new Promise((resolve) => { - setTimeout(() => { - console.log(`Sending SSE Event ${i + 1}:`, event.step); - onMessage({ - data: JSON.stringify(event) - }) - resolve() - }, delay) - }) - } - } -} +import { createSSESequence, issue619SseSequence, mockFetchEventSource } from '../../mocks/sse.mock' +import { replayProject, replayActiveTask } from '../../../src/lib' // Mock navigate function const mockNavigate = vi.fn() as any @@ -523,4 +504,304 @@ describe('Integration Test: Replay Functionality', () => { console.log('Both tasks ran independently with separate chatStores') }, { timeout: 3000 }) }) +}) + +//https://github.com/eigent-ai/eigent/issues/619 - Two task boxes +describe('Issue #619 - Duplicate Task Boxes after replay', () => { + beforeEach(() => { + vi.clearAllMocks(); + const { result } = renderHook(() => useProjectStore()); + //Reset projectStore + result.current.getAllProjects().forEach(project => { + result.current.removeProject(project.id) + }) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it("should create a separate chatStore for each replay", async () => { + const { result, rerender } = renderHook(() => useChatStoreAdapter()) + + let sseCallCount = 0 + + // Step 0: First simulate a replay mechanism to set up the scenario + const replaySequence = createSSESequence([ + { + event: { + step: 'confirmed', + data: { question: 'Previous calendar task replay' } + }, + delay: 50 + }, + { + event: { + step: 'to_sub_tasks', + data: { + summary_task: 'Calendar Replay|Previous calendar interaction', + sub_tasks: [ + { id: 'replay-cal-1', content: 'Check calendar access', status: 'completed' }, + { id: 'replay-cal-2', content: 'Fetch meeting data', status: 'completed' }, + ], + }, + }, + delay: 100 + }, + { + event: { + step: "end", + data: "--- Previous Calendar Task Replay Complete ---\nFound 3 upcoming meetings" + }, + delay: 150 + } + ]) + + // Mock SSE stream with controlled events - delay setup until after task IDs are available + mockFetchEventSource.mockImplementation(async (url: string, options: any) => { + sseCallCount++ + console.log(`SSE Call #${sseCallCount} initiated`) + + if (options.onmessage) { + // First simulate replay of previous event to establish context + if (sseCallCount === 1 && url.includes('/api/chat/steps/playback/')) { + console.log('Simulating replay mechanism for previous calendar task') + await replaySequence(options.onmessage) + return + } + + // Now send the new task events with actual task IDs + const immediateSequence = createSSESequence(issue619SseSequence) + await immediateSequence(options.onmessage) + } + }) + + // Simulate the replay mechanism first + console.log('Starting replay simulation to establish context...') + await act(async () => { + await replayProject( + result.current.projectStore, + mockNavigate, + generateUniqueId(), + 'Previous calendar task replay', + 'calendar-replay-history' + ) + rerender() + }) + + // Wait for replay to complete and verify it's established + await waitFor(() => { + rerender() + const { projectStore } = result.current + const projects = projectStore.getAllProjects() + const replayProject = projects.find((p: any) => p.name.includes('Replay Project')) + expect(replayProject).toBeDefined() + console.log('Replay mechanism completed - context established') + }, { timeout: 1000 }) + + // Get initial state + const { chatStore: initialChatStore, projectStore } = result.current + const projectId = projectStore.activeProjectId as string + const initiatorTaskId = initialChatStore.activeTaskId + + // Verify initial queue is empty + expect(projectStore.getProjectById(projectId)?.queuedMessages).toEqual([]) + + // Step 1: Start first task + await act(async () => { + const userMessage = 'Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.' + await initialChatStore.startTask(initiatorTaskId, undefined, undefined, undefined, userMessage) + rerender() + }) + + // Wait for task to start and reach 'to_sub_tasks' phase (task becomes busy) + await waitFor(() => { + rerender() + const { chatStore, projectStore } = result.current + const taskId = chatStore.activeTaskId + const task = chatStore.tasks[taskId] + + // Task should have subtasks (making it busy) + expect(task.summaryTask).toBe('Task|Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.') + console.log("Task info is ", task.taskInfo); + //Bcz of newTaskInfo { id: '', content: '', status: '' } we have 2 items + expect(task.taskInfo).toHaveLength(2) + + console.log("Task reached to_sub_tasks phase - now busy") + }, { timeout: 1500 }) + + //Waitfor end sse + await waitFor(() => { + rerender() + const { chatStore: finalChatStore, projectStore: finalProjectStore } = result.current; + const finalTaskId = finalChatStore.activeTaskId; + const finalTask = finalChatStore.tasks[finalTaskId]; + expect(finalTask.status).toBe('finished'); + }, { timeout: 10000 }); + + // Step 7: Verify final state + const { chatStore: finalChatStore, projectStore: finalProjectStore } = result.current + const finalProject = finalProjectStore.getProjectById(projectId) + + // Verify task completed successfully + const finalTaskId = finalChatStore.activeTaskId + const finalTask = finalChatStore.tasks[finalTaskId] + expect(finalTask.status).toBe('finished') + expect(finalTask.summaryTask).toBe('Task|Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.') + + console.log("Test completed - queue management verified: one task processed, one remains") + }) + + it("should have correct first question on replayActiveTask", async () => { + const { result, rerender } = renderHook(() => useChatStoreAdapter()) + const projectStoreResult = renderHook(() => useProjectStore()) + + let sseCallCount = 0 + const originalUserMessage = 'Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.' + + // Step 1: Create initial task with specific user message + const initialSequence = createSSESequence([ + { + event: { + step: 'confirmed', + data: { question: originalUserMessage } + }, + delay: 100 + }, + { + event: { + step: 'to_sub_tasks', + data: { + summary_task: 'Calendar Task|Check upcoming Google Calendar meetings', + sub_tasks: [ + { id: 'cal-1', content: 'Access Google Calendar', status: 'completed' }, + { id: 'cal-2', content: 'Fetch meeting details', status: 'completed' }, + ], + }, + }, + delay: 200 + }, + { + event: { + step: "end", + data: "--- Calendar Task Complete ---\nFound your next meeting: Team Standup at 2:00 PM" + }, + delay: 300 + } + ]) + + // Step 2: Setup replay sequence that should have same first question + const replaySequence = createSSESequence([ + { + event: { + step: 'confirmed', + data: { question: "Fall Back question" } + }, + delay: 100 + }, + { + event: { + step: 'to_sub_tasks', + data: { + summary_task: 'Calendar Task Replay|Replaying calendar meeting check', + sub_tasks: [ + { id: 'replay-cal-1', content: 'Access Google Calendar (replay)', status: 'completed' }, + { id: 'replay-cal-2', content: 'Fetch meeting details (replay)', status: 'completed' }, + ], + }, + }, + delay: 200 + }, + { + event: { + step: "end", + data: "--- Calendar Task Replay Complete ---\nReplayed: Found your next meeting" + }, + delay: 300 + } + ]) + + // Mock SSE to handle both initial task and replay + mockFetchEventSource.mockImplementation(async (url: string, options: any) => { + sseCallCount++ + console.log(`SSE Call #${sseCallCount}: ${url}`) + + if (options.onmessage) { + if (sseCallCount === 1) { + // First call: initial task + console.log('Processing initial task events') + await initialSequence(options.onmessage) + } else if (url.includes('/api/chat/steps/playback/')) { + // Subsequent calls: replay + console.log('Processing replay events') + await replaySequence(options.onmessage) + } + } + }) + + // Step 3: Start initial task + await act(async () => { + const { chatStore } = result.current + const taskId = chatStore.activeTaskId + await chatStore.startTask(taskId, undefined, undefined, undefined, originalUserMessage) + rerender() + }) + + // Wait for initial task to complete + await waitFor(() => { + rerender() + const { chatStore } = result.current + const taskId = chatStore.activeTaskId + const task = chatStore.tasks[taskId] + expect(task.status).toBe('finished') + expect(task.messages[0].content).toBe(originalUserMessage) + console.log('Initial task completed with user message:', task.messages[0].content) + }, { timeout: 2000 }) + + // Step 4: Get the completed chatStore for replay + const { chatStore: completedChatStore, projectStore } = result.current + const completedTaskId = completedChatStore.activeTaskId + const completedTask = completedChatStore.tasks[completedTaskId] + + // Verify we have the correct initial state + expect(completedTask.messages[0].content).toBe(originalUserMessage) + expect(completedTask.status).toBe('finished') + + // Step 5: Call replayActiveTask using the completed chatStore + await act(async () => { + await replayActiveTask(completedChatStore, projectStore, mockNavigate) + rerender() + }) + + // Step 6: Wait for replay to complete and verify first question matches + await waitFor(() => { + rerender() + const { projectStore: updatedProjectStore } = result.current + const projects = updatedProjectStore.getAllProjects() + + // Find the replay project + const replayProject = projects.find((p: any) => p.name.includes('Replay Project')) + expect(replayProject).toBeDefined() + + // Get the replay chatStore + const replayChatStores = updatedProjectStore.getAllChatStores(replayProject!.id) + expect(replayChatStores.length).toBeGreaterThan(1) + + const replayChatStore = replayChatStores[1].chatStore // Skip the empty initial one + const replayTaskId = replayChatStore.getState().activeTaskId + const replayTask = replayChatStore.getState().tasks[replayTaskId] + + // THE MAIN TEST: First question in replay should match original user message + expect(replayTask).toBeDefined() + expect(replayTask.messages[0].content).toBe(originalUserMessage) + expect(replayTask.type).toBe('replay') + + console.log('✅ Replay first question matches original:', replayTask.messages[0].content) + console.log('✅ Original user message:', originalUserMessage) + console.log('✅ Test passed: replayActiveTask preserves correct first question') + }, { timeout: 3000 }) + + // Verify navigation was called for replay + expect(mockNavigate).toHaveBeenCalledWith("/") + }) }) \ No newline at end of file diff --git a/test/mocks/sse.mock.ts b/test/mocks/sse.mock.ts index 2e6de25b9..c7255391e 100644 --- a/test/mocks/sse.mock.ts +++ b/test/mocks/sse.mock.ts @@ -5,4 +5,323 @@ export const mockFetchEventSource = vi.fn() vi.mock('@microsoft/fetch-event-source', () => ({ fetchEventSource: (...args: any[]) => mockFetchEventSource(...args), -})) \ No newline at end of file +})) + +// Helper function for sequential SSE events +export const createSSESequence = (events: Array<{ event: any; delay: number }>) => { + return async (onMessage: (data: any) => void) => { + for (let i = 0; i < events.length; i++) { + const { event, delay } = events[i] + + await new Promise((resolve) => { + setTimeout(() => { + console.log(`Sending SSE Event ${i + 1}:`, event.step); + onMessage({ + data: JSON.stringify(event) + }) + resolve() + }, delay) + }) + } + } +} + + +// https://github.com/eigent-ai/eigent/issues/619 - Two task boxes +export const issue619SseSequence = [ + { + event: { + step: 'confirmed', + data: { question: 'Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.' } + }, + delay: 100 + }, + { + event: { + step: 'to_sub_tasks', + data: { + summary_task: 'Task|Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.', + sub_tasks: [ + { + id: '1762503273000-2985.1', + content: 'Retrieve the details of the next scheduled event on the user\'s Google Calendar, including the start time, title of the meeting, and the list of all participants\' names and email addresses. Format the result as a JSON object with the keys: \'start_time\', \'meeting_title\', and \'participants\', where \'participants\' is an array of objects each with \'name\' and \'email\'.', + state: 'FAILED', + subtasks: [] + } + ] + } + }, + delay: 100 + }, + { + event: { + step: 'deactivate_agent', + data: { + agent_name: 'question_confirm_agent', + agent_id: '3f9fff03-e95a-4ad6-a3d4-6bdd73b639c4', + process_task_id: '', + message: 'yes', + tokens: 357 + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'task_agent', + agent_id: '17cef41b-5158-40e8-b1e2-0a88777dddb4', + tools: [] + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'search_agent', + agent_id: 'c249ac7b-6745-4832-a5c1-3238f9176434', + tools: ['Search Toolkit', 'Browser Toolkit', 'Human Toolkit', 'Note Taking Toolkit', 'Terminal Toolkit'] + } + }, + delay: 100 + }, + { + event: { + step: 'deactivate_toolkit', + data: { + agent_name: 'search_agent', + process_task_id: '', + toolkit_name: 'Browser Toolkit', + method_name: 'register agent', + message: 'null' + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'document_agent', + agent_id: 'e861cc5b-976b-41f0-a298-9c3e921ffaf5', + tools: ['File Toolkit', 'Pptx Toolkit', 'Human Toolkit', 'Mark It Down Toolkit', 'Excel Toolkit', 'Note Taking Toolkit', 'Terminal Toolkit', 'Google Drive Mcp Toolkit'] + } + }, + delay: 100 + }, + { + event: { + step: 'deactivate_toolkit', + data: { + agent_name: 'multi_modal_agent', + process_task_id: '', + toolkit_name: 'Open Ai Image Toolkit', + method_name: 'get openai credentials', + message: '[object Object]' + } + }, + delay: 100 + }, + { + event: { + step: 'activate_toolkit', + data: { + agent_name: 'search_agent', + process_task_id: '', + toolkit_name: 'Browser Toolkit', + method_name: 'register agent', + message: 'ChatAgent(Search Agent, RoleType.ASSISTANT, gpt-5)' + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'mcp_agent', + agent_id: 'd23a70fe-5c90-47bd-9170-f50defcd352a', + tools: [] + } + }, + delay: 100 + }, + { + event: { + step: 'activate_toolkit', + data: { + agent_name: 'search_agent', + process_task_id: '', + toolkit_name: 'Browser Toolkit', + method_name: 'register agent', + message: 'ChatAgent(Search Agent, RoleType.ASSISTANT, gpt-5)' + } + }, + delay: 100 + }, + { + event: { + step: 'activate_agent', + data: { + agent_name: 'question_confirm_agent', + process_task_id: '', + agent_id: '3f9fff03-e95a-4ad6-a3d4-6bdd73b639c4', + message: '=== Previous Conversation ===\nPrevious Task: Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.\n\n\nUser Query: Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.\n\nDetermine if this user query is a complex task or a simple question.\n\nComplex task (answer "yes"): Requires tools, code execution, file operations, multi-step planning, or creating/modifying content\n- Examples: "create a file", "search for X", "implement feature Y", "write code", "analyze data", "build something"\n\nSimple question (answer "no"): Can be answered directly with knowledge or conversation history, no action needed\n- Examples: greetings ("hello", "hi"), fact queries ("what is X?"), clarifications ("what did you mean?"), status checks ("how are you?")\n\nAnswer only "yes" or "no". Do not provide any explanation.\n\nIs this a complex task? (yes/no):' + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'coordinator_agent', + agent_id: '97326e19-91aa-4f9e-95ac-dbc1d92d3756', + tools: [] + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'new_worker_agent', + agent_id: '62eca7d0-ed56-4101-bd20-9c929d31590e', + tools: [] + } + }, + delay: 100 + }, + { + event: { + step: 'activate_toolkit', + data: { + agent_name: 'search_agent', + process_task_id: '', + toolkit_name: 'Browser Toolkit', + method_name: 'register agent', + message: 'ChatAgent(Search Agent, RoleType.ASSISTANT, gpt-4.1)' + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'developer_agent', + agent_id: 'd7514c96-3414-4220-9a99-c08cbabc86b5', + tools: ['Human Toolkit', 'Terminal Toolkit', 'Note Taking Toolkit', 'Web Deploy Toolkit'] + } + }, + delay: 100 + }, + { + event: { + step: 'activate_toolkit', + data: { + agent_name: 'multi_modal_agent', + process_task_id: '', + toolkit_name: 'Open Ai Image Toolkit', + method_name: 'get openai credentials', + message: '[object Object]' + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'multi_modal_agent', + agent_id: '2682c381-3560-4584-96c6-985a5395c85d', + tools: ['Video Downloader Toolkit', 'Audio Analysis Toolkit', 'Image Analysis Toolkit', 'Open Ai Image Toolkit', 'Human Toolkit', 'Terminal Toolkit', 'Note Taking Toolkit', 'Search Toolkit'] + } + }, + delay: 100 + }, + { + event: { + step: 'deactivate_toolkit', + data: { + agent_name: 'search_agent', + process_task_id: '', + toolkit_name: 'Browser Toolkit', + method_name: 'register agent', + message: 'null' + } + }, + delay: 100 + }, + { + event: { + step: 'create_agent', + data: { + agent_name: 'Google calendar agent ', + agent_id: '729ebd9b-4eef-4f4a-83b8-0289bef302c5', + tools: ['Google Calendar Toolkit'] + } + }, + delay: 1100 + }, + { + event: { + step: 'activate_toolkit', + data: { + agent_name: 'search_agent', + process_task_id: '', + toolkit_name: 'Browser Toolkit', + method_name: 'register agent', + message: 'ChatAgent(Search Agent, RoleType.ASSISTANT, gpt-4.1)' + } + }, + delay: 1100 + }, + { + event: { + step: 'deactivate_toolkit', + data: { + agent_name: 'Google calendar agent ', + process_task_id: '1762502461718-31.1', + toolkit_name: 'Google Calendar Toolkit', + method_name: 'get calendar details', + message: 'Failed to retrieve calendar details' + } + }, + delay: 1100 + }, + { + event: { + step: 'deactivate_toolkit', + data: { + agent_name: 'Google calendar agent ', + process_task_id: '1762502461718-31.1', + toolkit_name: 'Google Calendar Toolkit', + method_name: 'get events', + message: '{"error": "Failed to retrieve events: timed out"}' + } + }, + delay: 1100 + }, + { + event: { + step: 'to_sub_tasks', + data: { + summary_task: 'Task|Please help me check Google Calendar when is the next meeting, what kind of meeting it is, and who is attending the meeting.', + sub_tasks: [] + } + }, + delay: 1100 + }, + { + event: { + step: 'end', + data: '' + }, + delay: 1100 + } +]; From 04303666ccff1596d3e6df8407411679322dbcc3 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Tue, 11 Nov 2025 18:12:20 +0800 Subject: [PATCH 05/10] fix: preserve login state when dependencies need to be installed --- electron/main/index.ts | 80 +++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 5088c5105..20a941a39 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1180,57 +1180,57 @@ async function createWindow() { // Handle localStorage based on installation state if (needsInstallation) { - log.info('Installation needed - clearing auth storage to force carousel state'); - - // Clear the persisted auth storage file to force fresh initialization with carousel - // Main window uses partition 'persist:main_window', so data is in Partitions/main_window - const partitionPath = path.join(app.getPath('userData'), 'Partitions', 'main_window'); - const localStoragePath = path.join(partitionPath, 'Local Storage'); - const leveldbPath = path.join(localStoragePath, 'leveldb'); - - try { - // Delete the localStorage database to force fresh init - if (fs.existsSync(leveldbPath)) { - log.info('Removing localStorage database to force fresh state...'); - fs.rmSync(leveldbPath, { recursive: true, force: true }); - log.info('Successfully cleared localStorage'); - } - } catch (error) { - log.error('Error clearing localStorage:', error); - } + log.info('Installation needed - resetting initState to carousel while preserving auth data'); + // Instead of deleting the entire localStorage, we'll update only the initState + // This preserves login information while resetting the initialization flow // Set up the injection for when page loads win.webContents.once('dom-ready', () => { if (!win || win.isDestroyed()) { log.warn('Window destroyed before DOM ready - skipping localStorage injection'); return; } - log.info('DOM ready - creating auth-storage with carousel state'); + log.info('DOM ready - updating initState to carousel while preserving auth data'); win.webContents.executeJavaScript(` (function() { try { - // Create fresh auth storage with carousel state - const newAuthStorage = { - state: { - token: null, - username: null, - email: null, - user_id: null, - appearance: 'light', - language: 'system', - isFirstLaunch: true, - modelType: 'cloud', - cloud_model_type: 'gpt-4.1', - initState: 'carousel', - share_token: null, - workerListData: {} - }, - version: 0 - }; - localStorage.setItem('auth-storage', JSON.stringify(newAuthStorage)); - console.log('[ELECTRON PRE-INJECT] Created fresh auth-storage with carousel state'); + const authStorage = localStorage.getItem('auth-storage'); + if (authStorage) { + // Preserve existing auth data, only update initState + const parsed = JSON.parse(authStorage); + const updatedStorage = { + ...parsed, + state: { + ...parsed.state, + initState: 'carousel' + } + }; + localStorage.setItem('auth-storage', JSON.stringify(updatedStorage)); + console.log('[ELECTRON PRE-INJECT] Updated initState to carousel, preserved auth data'); + } else { + // No existing storage, create new one with carousel state + const newAuthStorage = { + state: { + token: null, + username: null, + email: null, + user_id: null, + appearance: 'light', + language: 'system', + isFirstLaunch: true, + modelType: 'cloud', + cloud_model_type: 'gpt-4.1', + initState: 'carousel', + share_token: null, + workerListData: {} + }, + version: 0 + }; + localStorage.setItem('auth-storage', JSON.stringify(newAuthStorage)); + console.log('[ELECTRON PRE-INJECT] Created fresh auth-storage with carousel state'); + } } catch (e) { - console.error('[ELECTRON PRE-INJECT] Failed to create storage:', e); + console.error('[ELECTRON PRE-INJECT] Failed to update storage:', e); } })(); `).catch(err => { From 69e02194dbd35e5a418a07583c2003b496714703 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Wed, 12 Nov 2025 01:34:53 +0800 Subject: [PATCH 06/10] enhance and bug fix --- src/lib/replay.ts | 6 +++--- src/store/projectStore.ts | 44 +++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/lib/replay.ts b/src/lib/replay.ts index f5278eb6e..fcb4ed261 100644 --- a/src/lib/replay.ts +++ b/src/lib/replay.ts @@ -59,10 +59,10 @@ export const replayActiveTask = async ( if (project && project.chatStores) { Object.entries(project.chatStores).forEach(([chatStoreId, chatStoreData]: [string, any]) => { const timestamp = project.chatStoreTimestamps[chatStoreId] || 0; - chatStoreData = chatStoreData.getState(); + const chatState = chatStoreData.getState(); - if (chatStoreData.tasks) { - Object.values(chatStoreData.tasks).forEach((task: any) => { + if (chatState.tasks) { + Object.values(chatState.tasks).forEach((task: any) => { // Check messages for user content if (task.messages && task.messages.length > 0) { const userMessage = task.messages.find((msg: any) => msg.role === 'user'); diff --git a/src/store/projectStore.ts b/src/store/projectStore.ts index 6fcdb22ab..c7273d95d 100644 --- a/src/store/projectStore.ts +++ b/src/store/projectStore.ts @@ -473,27 +473,35 @@ const projectStore = create()((set, get) => ({ } console.log(`[ProjectStore] Created replay project ${replayProjectId} for ${taskIds.length} tasks`); - + // For each taskId, create a chat store within the project and call replay - taskIds.forEach(async (taskId, index) => { - console.log(`[ProjectStore] Creating replay for task ${index + 1}/${taskIds.length}: ${taskId}`); - - // Create a new chat store for this task - const chatId = createChatStore(replayProjectId, `Task ${taskId}`); - - if (chatId) { - const project = get().projects[replayProjectId]; - const chatStore = project.chatStores[chatId]; - - if (chatStore) { - // Call replay on the chat store with the taskId, question, and 0 delay - await chatStore.getState().replay(taskId, question, 0.2); - console.log(`[ProjectStore] Started replay for task ${taskId}`); + // Use for...of loop instead of forEach to properly handle async operations + (async () => { + for (let index = 0; index < taskIds.length; index++) { + const taskId = taskIds[index]; + console.log(`[ProjectStore] Creating replay for task ${index + 1}/${taskIds.length}: ${taskId}`); + + // Create a new chat store for this task + const chatId = createChatStore(replayProjectId, `Task ${taskId}`); + + if (chatId) { + const project = get().projects[replayProjectId]; + const chatStore = project.chatStores[chatId]; + + if (chatStore) { + // Call replay on the chat store with the taskId, question, and 0 delay + try { + await chatStore.getState().replay(taskId, question, 0.2); + console.log(`[ProjectStore] Started replay for task ${taskId}`); + } catch (error) { + console.error(`[ProjectStore] Failed to replay task ${taskId}:`, error); + } + } } } - }); - - console.log(`[ProjectStore] Completed replay setup for ${taskIds.length} tasks`); + console.log(`[ProjectStore] Completed replay setup for ${taskIds.length} tasks`); + })(); + return replayProjectId; }, From b244b476ccb5fc342d5b87ba37251fb4f60c2951 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Wed, 12 Nov 2025 01:35:58 +0800 Subject: [PATCH 07/10] update --- src/store/projectStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/store/projectStore.ts b/src/store/projectStore.ts index c7273d95d..78fa237e2 100644 --- a/src/store/projectStore.ts +++ b/src/store/projectStore.ts @@ -475,7 +475,6 @@ const projectStore = create()((set, get) => ({ console.log(`[ProjectStore] Created replay project ${replayProjectId} for ${taskIds.length} tasks`); // For each taskId, create a chat store within the project and call replay - // Use for...of loop instead of forEach to properly handle async operations (async () => { for (let index = 0; index < taskIds.length; index++) { const taskId = taskIds[index]; From ddfb4c046aa3117d3836e11313ba4001334f87e5 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Wed, 12 Nov 2025 15:28:33 +0800 Subject: [PATCH 08/10] enhance: preserve login state when dependencies need to be installed PR636 --- electron/main/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index 20a941a39..db2c2abc8 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1282,9 +1282,9 @@ async function createWindow() { } })(); `).then(needsReload => { - if (needsReload) { + if (needsReload && win && !win.isDestroyed()) { log.info('Reloading window after localStorage update'); - win!.reload(); + win.reload(); } }).catch(err => { log.error('Failed to inject script:', err); From 640a50725874a98a77016135fe504f721c5bbd03 Mon Sep 17 00:00:00 2001 From: sw3205933776 <3205933776@qq.com> Date: Wed, 12 Nov 2025 18:11:07 +0800 Subject: [PATCH 09/10] feat: show document agent workspace only when agent has tasks --- src/components/WorkFlow/node.tsx | 3 ++- utils/__pycache__/__init__.cpython-310.pyc | Bin 214 -> 193 bytes .../traceroot_wrapper.cpython-310.pyc | Bin 2544 -> 2523 bytes 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/WorkFlow/node.tsx b/src/components/WorkFlow/node.tsx index 512e836f8..b6af9eca1 100644 --- a/src/components/WorkFlow/node.tsx +++ b/src/components/WorkFlow/node.tsx @@ -451,7 +451,8 @@ export function Node({ id, data }: NodeProps) { )} )} - {data.type === "document_agent" && ( + {data.type === "document_agent" && + data?.agent?.tasks && data.agent.tasks.length > 0 && (
diff --git a/utils/__pycache__/__init__.cpython-310.pyc b/utils/__pycache__/__init__.cpython-310.pyc index 57ecb1ad7b408569a80294e611f22351efca17cb..10d1d4a69c028b0fa2e5b31e6afd01340e2b871d 100644 GIT binary patch delta 49 zcmcb{c#x4hpO=@50SMMha!ll|l2UQDig7F`%FjwoE{RFaOi#@#i773~%qfnUxJ3y7 DNuv*y delta 70 zcmX@ec#V-epO=@50SFE^@=WBe(znzPElw>e)=$YVP0vXz)^|xQ&MwI>(09&HNmX!4 YEKb!=%}h_tE7314$;>I%pEzF$0P#E*KL7v# diff --git a/utils/__pycache__/traceroot_wrapper.cpython-310.pyc b/utils/__pycache__/traceroot_wrapper.cpython-310.pyc index b8d9747f6f81fdae3f1f8ee704ee23bf51b8513c..d695ee21dc2c1d82d5dfa3d82d7694ce6bef45e3 100644 GIT binary patch delta 52 zcmew$d|Q}1pO=@50SMMha%|*&#U!QcY!%~JP?VpQnp_f-nwg%OR}xcNl9^K+vss1t G3>yHFvk=Vy delta 73 zcmcaD{6UyIpO=@50SFE^@@(XO#iZ|~A6lGRRIHzpUz(niSgh}oTAW>yU!d=tpOUKJ blvtdqpPHGTnpdJ Date: Thu, 13 Nov 2025 17:46:59 +0800 Subject: [PATCH 10/10] minor update --- src/components/WorkFlow/node.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/WorkFlow/node.tsx b/src/components/WorkFlow/node.tsx index b6af9eca1..3af1e022e 100644 --- a/src/components/WorkFlow/node.tsx +++ b/src/components/WorkFlow/node.tsx @@ -332,7 +332,7 @@ export function Node({ id, data }: NodeProps) { ? `${agentMap[data.type]?.borderColor} z-50` : "border-worker-border-default z-10" } transition-all duration-300 ease-in-out ${ - data.agent?.tasks.length === 0 && "opacity-30" + (data.agent?.tasks?.length ?? 0) === 0 && "opacity-30" }`} >
)} - {data.type === "document_agent" && - data?.agent?.tasks && data.agent.tasks.length > 0 && ( -
-
- + {data.type === "document_agent" && + data?.agent?.tasks && + data.agent.tasks.length > 0 && ( +
+
+ +
-
- )} + )} {data.type === "developer_agent" && data?.agent?.tasks &&