From a23f5c5778b57883fc5512e9667aaa4e71bd8629 Mon Sep 17 00:00:00 2001 From: puzhen <1303385763@qq.com> Date: Wed, 21 Jan 2026 19:35:31 +0000 Subject: [PATCH] initial fix --- backend/app/service/chat_service.py | 33 +++++++++++++----- electron/main/index.ts | 54 +++++++++++++++++++++++++++-- src/store/chatStore.ts | 51 +++++++++++++++++++-------- 3 files changed, 112 insertions(+), 26 deletions(-) diff --git a/backend/app/service/chat_service.py b/backend/app/service/chat_service.py index 04accf8b..4291abb7 100644 --- a/backend/app/service/chat_service.py +++ b/backend/app/service/chat_service.py @@ -517,7 +517,10 @@ async def step_solve(options: Chat, request: Request, task_lock: TaskLock): if not sub_tasks: sub_tasks = getattr(task_lock, "decompose_sub_tasks", []) sub_tasks = update_sub_tasks(sub_tasks, update_tasks) - add_sub_tasks(camel_task, item.data.task) + # Add new tasks (with empty id) to both camel_task and sub_tasks + new_tasks = add_sub_tasks(camel_task, item.data.task) + # Also add new tasks to sub_tasks so workforce.eigent_start uses correct list + sub_tasks.extend(new_tasks) summary_task_content_local = getattr(task_lock, "summary_task_content", summary_task_content) yield to_sub_tasks(camel_task, summary_task_content_local) elif item.action == Action.add_task: @@ -1086,6 +1089,10 @@ def tree_sub_tasks(sub_tasks: list[Task], depth: int = 0): def update_sub_tasks(sub_tasks: list[Task], update_tasks: dict[str, TaskContent], depth: int = 0): + logger.info(f"[update_sub_tasks] Called with {len(sub_tasks)} sub_tasks, {len(update_tasks)} update_tasks, depth={depth}") + logger.info(f"[update_sub_tasks] sub_tasks ids: {[t.id for t in sub_tasks]}") + logger.info(f"[update_sub_tasks] update_tasks keys: {list(update_tasks.keys())}") + if depth > 5: # limit the depth of the recursion return [] @@ -1093,23 +1100,33 @@ def update_sub_tasks(sub_tasks: list[Task], update_tasks: dict[str, TaskContent] while i < len(sub_tasks): item = sub_tasks[i] if item.id in update_tasks: + logger.info(f"[update_sub_tasks] Updating task {item.id}: {item.content[:30]}... -> {update_tasks[item.id].content[:30]}...") item.content = update_tasks[item.id].content update_sub_tasks(item.subtasks, update_tasks, depth + 1) i += 1 else: + logger.info(f"[update_sub_tasks] Removing task {item.id}: {item.content[:30]}... (not in update_tasks)") sub_tasks.pop(i) + logger.info(f"[update_sub_tasks] After update: {len(sub_tasks)} tasks remain") return sub_tasks -def add_sub_tasks(camel_task: Task, update_tasks: list[TaskContent]): - for item in update_tasks: +def add_sub_tasks(camel_task: Task, update_tasks: list[TaskContent]) -> list[Task]: + """Add new tasks (with empty id) to camel_task and return the list of added tasks.""" + logger.info(f"[add_sub_tasks] Called with {len(update_tasks)} update_tasks") + added_tasks = [] + for i, item in enumerate(update_tasks): + logger.info(f"[add_sub_tasks] Task {i}: id='{item.id}', content='{item.content[:30]}...'") if item.id == "": # - camel_task.add_subtask( - Task( - content=item.content, - id=f"{camel_task.id}.{len(camel_task.subtasks) + 1}", - ) + logger.info(f"[add_sub_tasks] Adding new task: {item.content[:30]}...") + new_task = Task( + content=item.content, + id=f"{camel_task.id}.{len(camel_task.subtasks) + 1}", ) + camel_task.add_subtask(new_task) + added_tasks.append(new_task) + logger.info(f"[add_sub_tasks] Added {len(added_tasks)} new tasks") + return added_tasks async def question_confirm(agent: ListenChatAgent, prompt: str, task_lock: TaskLock | None = None) -> bool: diff --git a/electron/main/index.ts b/electron/main/index.ts index 7b3c1d45..b28ac714 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1275,8 +1275,9 @@ async function createWindow() { minWidth: 1050, minHeight: 650, frame: false, - transparent: true, - backgroundColor: '#00000000', + show: false, // Don't show until content is ready + transparent: process.platform === 'darwin', // Only transparent on macOS for vibrancy effects + backgroundColor: process.platform === 'darwin' ? '#00000000' : '#1a1a2e', // Visible fallback on non-macOS titleBarStyle: isMac ? 'hidden' : undefined, trafficLightPosition: isMac ? { x: 10, y: 10 } : undefined, icon: path.join(VITE_PUBLIC, 'favicon.ico'), @@ -1316,6 +1317,41 @@ async function createWindow() { }); } + // ==================== Handle renderer crashes and failed loads ==================== + win.webContents.on('render-process-gone', (event, details) => { + log.error('[RENDERER] Process gone:', details.reason, details.exitCode); + if (win && !win.isDestroyed()) { + // Reload the window after a brief delay + setTimeout(() => { + if (win && !win.isDestroyed()) { + log.info('[RENDERER] Attempting to reload after crash...'); + if (VITE_DEV_SERVER_URL) { + win.loadURL(VITE_DEV_SERVER_URL); + } else { + win.loadFile(indexHtml); + } + } + }, 1000); + } + }); + + win.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => { + log.error(`[RENDERER] Failed to load: ${errorCode} - ${errorDescription} - ${validatedURL}`); + // Retry loading after a delay + if (errorCode !== -3) { // -3 is USER_CANCELLED, don't retry + setTimeout(() => { + if (win && !win.isDestroyed()) { + log.info('[RENDERER] Retrying load after failure...'); + if (VITE_DEV_SERVER_URL) { + win.loadURL(VITE_DEV_SERVER_URL); + } else { + win.loadFile(indexHtml); + } + } + }, 2000); + } + }); + // Main window now uses default userData directly with partition 'persist:main_window' // No migration needed - data is already persistent @@ -1563,9 +1599,15 @@ async function createWindow() { win.loadFile(indexHtml); } - // Wait for window to be ready + // Wait for window to be ready with timeout await new Promise((resolve) => { + const loadTimeout = setTimeout(() => { + log.warn('Window content load timeout (10s), showing window anyway...'); + resolve(); + }, 10000); + win!.webContents.once('did-finish-load', () => { + clearTimeout(loadTimeout); log.info( 'Window content loaded, starting dependency check immediately...' ); @@ -1573,6 +1615,12 @@ async function createWindow() { }); }); + // Show window now that content is loaded (or timeout reached) + if (win && !win.isDestroyed()) { + win.show(); + log.info('Window shown after content loaded'); + } + // Mark window as ready and process any queued protocol URLs isWindowReady = true; log.info('Window is ready, processing queued protocol URLs...'); diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts index 6cc93c98..1ccdad0b 100644 --- a/src/store/chatStore.ts +++ b/src/store/chatStore.ts @@ -845,6 +845,13 @@ const chatStore = (initial?: Partial) => createStore()( clearStreamingDecomposeText(currentTaskId); // Clean up TTFT tracking delete ttftTracking[currentTaskId]; + + // Check if task is already confirmed - don't overwrite user edits + const existingToSubTasksMessage = tasks[currentTaskId].messages.findLast((m: Message) => m.step === 'to_sub_tasks'); + if (existingToSubTasksMessage?.isConfirm) { + return; + } + // Check if this is a multi-turn scenario after task completion const isMultiTurnAfterCompletion = tasks[currentTaskId].status === 'finished'; @@ -2146,20 +2153,16 @@ const chatStore = (initial?: Partial) => createStore()( // record task start time setTaskTime(taskId, Date.now()); + // Filter out empty tasks from the user-edited taskInfo const taskInfo = tasks[taskId].taskInfo.filter((task) => task.content !== '') + console.log('[handleConfirmTask] Original taskInfo from store:', tasks[taskId].taskInfo.map((t, i) => `${i}: ${t.content?.slice(0, 40)}`)); + console.log('[handleConfirmTask] Filtered taskInfo to send:', taskInfo.map((t, i) => `${i}: ${t.content?.slice(0, 40)}`)); setTaskInfo(taskId, taskInfo) - // Also update taskRunning with the filtered tasks to keep counts consistent - const taskRunning = tasks[taskId].taskRunning.filter((task) => task.content !== '') - setTaskRunning(taskId, taskRunning) - if (!type) { - await fetchPut(`/task/${project_id}`, { - task: taskInfo, - }); - await fetchPost(`/task/${project_id}/start`, {}); + // Sync taskRunning with the filtered taskInfo (user edits should be reflected in execution) + setTaskRunning(taskId, taskInfo) - setActiveWorkSpace(taskId, 'workflow') - setStatus(taskId, 'running') - } + // IMPORTANT: Set isConfirm BEFORE sending API requests to prevent race condition + // where backend sends to_sub_tasks SSE event before we mark task as confirmed let messages = [...tasks[taskId].messages] const cardTaskIndex = messages.findLastIndex((message) => message.step === 'to_sub_tasks') if (cardTaskIndex !== -1) { @@ -2171,11 +2174,23 @@ const chatStore = (initial?: Partial) => createStore()( setMessages(taskId, messages) } + if (!type) { + console.log('[handleConfirmTask] Sending to backend PUT /task:', taskInfo.map((t, i) => `${i}: ${t.content?.slice(0, 40)}`)); + await fetchPut(`/task/${project_id}`, { + task: taskInfo, + }); + await fetchPost(`/task/${project_id}/start`, {}); + + setActiveWorkSpace(taskId, 'workflow') + setStatus(taskId, 'running') + } + // Reset editing state after manual confirmation so next round can auto-start setIsTaskEdit(taskId, false); }, addTaskInfo() { const { tasks, activeTaskId, setTaskInfo } = get() + console.log('[addTaskInfo] Called, activeTaskId:', activeTaskId); if (!activeTaskId) return let targetTaskInfo = [...tasks[activeTaskId].taskInfo] const newTaskInfo = { @@ -2183,6 +2198,7 @@ const chatStore = (initial?: Partial) => createStore()( content: "", }; targetTaskInfo.push(newTaskInfo) + console.log('[addTaskInfo] New taskInfo length:', targetTaskInfo.length); setTaskInfo(activeTaskId, targetTaskInfo) }, addTerminal(taskId, processTaskId, terminal) { @@ -2322,21 +2338,26 @@ const chatStore = (initial?: Partial) => createStore()( }, updateTaskInfo(index: number, content: string) { const { tasks, activeTaskId, setTaskInfo } = get() + console.log('[updateTaskInfo] Called, activeTaskId:', activeTaskId, 'index:', index, 'content:', content?.slice(0, 30)); if (!activeTaskId) return - let targetTaskInfo = [...tasks[activeTaskId].taskInfo] - if (targetTaskInfo) { - targetTaskInfo[index].content = content - } + // Deep copy the array with updated item to ensure React detects the change + const targetTaskInfo = tasks[activeTaskId].taskInfo.map((item, i) => + i === index ? { ...item, content } : item + ) + console.log('[updateTaskInfo] Updated taskInfo:', targetTaskInfo.map((t, i) => `${i}: ${t.content?.slice(0, 30)}`)); setTaskInfo(activeTaskId, targetTaskInfo) }, deleteTaskInfo(index: number) { const { tasks, activeTaskId, setTaskInfo } = get() + console.log('[deleteTaskInfo] Called, activeTaskId:', activeTaskId, 'index:', index); if (!activeTaskId) return + console.log('[deleteTaskInfo] Before delete:', tasks[activeTaskId].taskInfo.map((t, i) => `${i}: ${t.content?.slice(0, 30)}`)); let targetTaskInfo = [...tasks[activeTaskId].taskInfo] if (targetTaskInfo) { targetTaskInfo.splice(index, 1) } + console.log('[deleteTaskInfo] After delete:', targetTaskInfo.map((t, i) => `${i}: ${t.content?.slice(0, 30)}`)); setTaskInfo(activeTaskId, targetTaskInfo) },