diff --git a/backend/app/controller/electron_browser.cjs b/backend/app/controller/electron_browser.cjs
new file mode 100644
index 00000000..5b4fc698
--- /dev/null
+++ b/backend/app/controller/electron_browser.cjs
@@ -0,0 +1,393 @@
+const { app, BrowserWindow, ipcMain } = require('electron');
+const path = require('path');
+
+// Parse command line arguments
+const args = process.argv.slice(2);
+const userDataDir = args[0];
+const cdpPort = args[1];
+const startUrl = args[2] || 'https://www.google.com';
+
+// This must be called before app.ready
+app.commandLine.appendSwitch('remote-debugging-port', cdpPort);
+
+console.log('[ELECTRON BROWSER] Starting with:');
+console.log(' Chrome version:', process.versions.chrome);
+console.log(' User data dir (requested):', userDataDir);
+console.log(' CDP port:', cdpPort);
+console.log(' Start URL:', startUrl);
+
+// Set app paths - must be done before app.ready
+// Do NOT use commandLine.appendSwitch('user-data-dir') as it conflicts with setPath
+app.setPath('userData', userDataDir);
+app.setPath('sessionData', userDataDir);
+
+app.whenReady().then(async () => {
+ const { session } = require('electron');
+ const fs = require('fs');
+ const path = require('path');
+
+ // Log actual paths being used
+ console.log('[ELECTRON BROWSER] Actual paths:');
+ console.log(' app.getPath("userData"):', app.getPath('userData'));
+ console.log(' app.getPath("sessionData"):', app.getPath('sessionData'));
+ console.log(' app.getPath("cache"):', app.getPath('cache'));
+ console.log(' app.getPath("temp"):', app.getPath('temp'));
+ console.log(' process.argv:', process.argv);
+
+ // Check command line switches
+ console.log('[ELECTRON BROWSER] Command line switches:');
+ console.log(' user-data-dir:', app.commandLine.getSwitchValue('user-data-dir'));
+ console.log(' remote-debugging-port:', app.commandLine.getSwitchValue('remote-debugging-port'));
+
+ // Log partition session info
+ const userLoginSession = session.fromPartition('persist:user_login');
+ console.log('[ELECTRON BROWSER] Session info:');
+ console.log(' Partition: persist:user_login');
+ console.log(' Session storage path:', userLoginSession.getStoragePath());
+
+ // Check if Cookies file exists
+ const cookiesPath = path.join(app.getPath('userData'), 'Partitions', 'user_login', 'Cookies');
+ console.log('[ELECTRON BROWSER] Cookies path:', cookiesPath);
+ console.log('[ELECTRON BROWSER] Cookies exists:', fs.existsSync(cookiesPath));
+ if (fs.existsSync(cookiesPath)) {
+ const stats = fs.statSync(cookiesPath);
+ console.log('[ELECTRON BROWSER] Cookies file size:', stats.size, 'bytes');
+ }
+ const win = new BrowserWindow({
+ width: 1400,
+ height: 900,
+ title: 'Eigent Browser - Login',
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ webviewTag: true
+ }
+ });
+
+ // Create navigation bar and webview HTML
+ const html = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Browser Info
+
+
+
+
+
+
+`;
+
+ win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(html));
+
+ // Show window when ready
+ win.once('ready-to-show', () => {
+ win.show();
+
+ // Log cookies periodically to track changes
+ setInterval(async () => {
+ try {
+ const cookies = await userLoginSession.cookies.get({});
+ console.log('[ELECTRON BROWSER] Current cookies count:', cookies.length);
+ if (cookies.length > 0) {
+ console.log('[ELECTRON BROWSER] Cookie domains:', [...new Set(cookies.map(c => c.domain))]);
+ }
+ } catch (error) {
+ console.error('[ELECTRON BROWSER] Failed to get cookies:', error);
+ }
+ }, 5000); // Check every 5 seconds
+ });
+
+ win.on('closed', async () => {
+ console.log('[ELECTRON BROWSER] Window closed, preparing to quit...');
+
+ // Flush storage data before quitting to ensure cookies are saved
+ try {
+ const { session } = require('electron');
+ const fs = require('fs');
+ const path = require('path');
+ const userLoginSession = session.fromPartition('persist:user_login');
+
+ // Log cookies before flush
+ const cookiesBeforeFlush = await userLoginSession.cookies.get({});
+ console.log('[ELECTRON BROWSER] Cookies count before flush:', cookiesBeforeFlush.length);
+
+ // Flush storage
+ console.log('[ELECTRON BROWSER] Flushing storage data...');
+ await userLoginSession.flushStorageData();
+ console.log('[ELECTRON BROWSER] Storage data flushed successfully');
+
+ // Check cookies file after flush
+ const cookiesPath = path.join(app.getPath('userData'), 'Partitions', 'user_login', 'Cookies');
+ if (fs.existsSync(cookiesPath)) {
+ const stats = fs.statSync(cookiesPath);
+ console.log('[ELECTRON BROWSER] Cookies file size after flush:', stats.size, 'bytes');
+ } else {
+ console.log('[ELECTRON BROWSER] WARNING: Cookies file does not exist after flush!');
+ }
+ } catch (error) {
+ console.error('[ELECTRON BROWSER] Failed to flush storage data:', error);
+ }
+ app.quit();
+ });
+});
+
+let isQuitting = false;
+
+app.on('before-quit', async (event) => {
+ if (isQuitting) return;
+
+ // Prevent immediate quit to allow storage flush and cookie sync
+ event.preventDefault();
+ isQuitting = true;
+
+ console.log('[ELECTRON BROWSER] before-quit event triggered');
+
+ try {
+ const { session } = require('electron');
+ const fs = require('fs');
+ const path = require('path');
+ const userLoginSession = session.fromPartition('persist:user_login');
+
+ // Log cookies before flush
+ const cookiesBeforeQuit = await userLoginSession.cookies.get({});
+ console.log('[ELECTRON BROWSER] Cookies count before quit:', cookiesBeforeQuit.length);
+ if (cookiesBeforeQuit.length > 0) {
+ console.log('[ELECTRON BROWSER] Cookie domains before quit:', [...new Set(cookiesBeforeQuit.map(c => c.domain))]);
+ }
+
+ // Flush storage
+ console.log('[ELECTRON BROWSER] Flushing storage on quit...');
+ await userLoginSession.flushStorageData();
+ console.log('[ELECTRON BROWSER] Storage data flushed on quit');
+ } catch (error) {
+ console.error('[ELECTRON BROWSER] Failed to sync cookies:', error);
+ } finally {
+ console.log('[ELECTRON BROWSER] Exiting now...');
+ // Force quit after sync
+ app.exit(0);
+ }
+});
+
+app.on('window-all-closed', () => {
+ if (!isQuitting) {
+ app.quit();
+ }
+});
diff --git a/backend/app/controller/tool_controller.py b/backend/app/controller/tool_controller.py
index 29320da5..b9cb7ffd 100644
--- a/backend/app/controller/tool_controller.py
+++ b/backend/app/controller/tool_controller.py
@@ -347,410 +347,13 @@ async def open_browser_login():
"note": "Your login data will be saved in the profile."
}
- # Create Electron browser script with .cjs extension for CommonJS
+ # Use static Electron browser script
electron_script_path = os.path.join(os.path.dirname(__file__), "electron_browser.cjs")
- electron_script_content = '''
-const { app, BrowserWindow, ipcMain } = require('electron');
-const path = require('path');
-// Parse command line arguments
-const args = process.argv.slice(2);
-const userDataDir = args[0];
-const cdpPort = args[1];
-const startUrl = args[2] || 'https://www.google.com';
+ # Verify script exists
+ if not os.path.exists(electron_script_path):
+ raise FileNotFoundError(f"Electron browser script not found: {electron_script_path}")
-// This must be called before app.ready
-app.commandLine.appendSwitch('remote-debugging-port', cdpPort);
-
-console.log('[ELECTRON BROWSER] Starting with:');
-console.log(' Chrome version:', process.versions.chrome);
-console.log(' User data dir (requested):', userDataDir);
-console.log(' CDP port:', cdpPort);
-console.log(' Start URL:', startUrl);
-
-// Set app paths - must be done before app.ready
-// Do NOT use commandLine.appendSwitch('user-data-dir') as it conflicts with setPath
-app.setPath('userData', userDataDir);
-app.setPath('sessionData', userDataDir);
-
-app.whenReady().then(async () => {
- const { session } = require('electron');
- const fs = require('fs');
- const path = require('path');
-
- // Log actual paths being used
- console.log('[ELECTRON BROWSER] Actual paths:');
- console.log(' app.getPath("userData"):', app.getPath('userData'));
- console.log(' app.getPath("sessionData"):', app.getPath('sessionData'));
- console.log(' app.getPath("cache"):', app.getPath('cache'));
- console.log(' app.getPath("temp"):', app.getPath('temp'));
- console.log(' process.argv:', process.argv);
-
- // Check command line switches
- console.log('[ELECTRON BROWSER] Command line switches:');
- console.log(' user-data-dir:', app.commandLine.getSwitchValue('user-data-dir'));
- console.log(' remote-debugging-port:', app.commandLine.getSwitchValue('remote-debugging-port'));
-
- // Log partition session info
- const userLoginSession = session.fromPartition('persist:user_login');
- console.log('[ELECTRON BROWSER] Session info:');
- console.log(' Partition: persist:user_login');
- console.log(' Session storage path:', userLoginSession.getStoragePath());
-
- // Check if Cookies file exists
- const cookiesPath = path.join(app.getPath('userData'), 'Partitions', 'user_login', 'Cookies');
- console.log('[ELECTRON BROWSER] Cookies path:', cookiesPath);
- console.log('[ELECTRON BROWSER] Cookies exists:', fs.existsSync(cookiesPath));
- if (fs.existsSync(cookiesPath)) {
- const stats = fs.statSync(cookiesPath);
- console.log('[ELECTRON BROWSER] Cookies file size:', stats.size, 'bytes');
- }
- const win = new BrowserWindow({
- width: 1400,
- height: 900,
- title: 'Eigent Browser - Login',
- webPreferences: {
- nodeIntegration: true,
- contextIsolation: false,
- webviewTag: true
- }
- });
-
- // Create navigation bar and webview
- const html = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Browser Info
-
-
-
-
-
-
-`;
-
- win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(html));
-
- // Show window when ready
- win.once('ready-to-show', () => {
- win.show();
-
- // Log cookies periodically to track changes
- setInterval(async () => {
- try {
- const cookies = await userLoginSession.cookies.get({});
- console.log('[ELECTRON BROWSER] Current cookies count:', cookies.length);
- if (cookies.length > 0) {
- console.log('[ELECTRON BROWSER] Cookie domains:', [...new Set(cookies.map(c => c.domain))]);
- }
- } catch (error) {
- console.error('[ELECTRON BROWSER] Failed to get cookies:', error);
- }
- }, 5000); // Check every 5 seconds
- });
-
- win.on('closed', async () => {
- console.log('[ELECTRON BROWSER] Window closed, preparing to quit...');
-
- // Flush storage data before quitting to ensure cookies are saved
- try {
- const { session } = require('electron');
- const fs = require('fs');
- const path = require('path');
- const userLoginSession = session.fromPartition('persist:user_login');
-
- // Log cookies before flush
- const cookiesBeforeFlush = await userLoginSession.cookies.get({});
- console.log('[ELECTRON BROWSER] Cookies count before flush:', cookiesBeforeFlush.length);
-
- // Flush storage
- console.log('[ELECTRON BROWSER] Flushing storage data...');
- await userLoginSession.flushStorageData();
- console.log('[ELECTRON BROWSER] Storage data flushed successfully');
-
- // Check cookies file after flush
- const cookiesPath = path.join(app.getPath('userData'), 'Partitions', 'user_login', 'Cookies');
- if (fs.existsSync(cookiesPath)) {
- const stats = fs.statSync(cookiesPath);
- console.log('[ELECTRON BROWSER] Cookies file size after flush:', stats.size, 'bytes');
- } else {
- console.log('[ELECTRON BROWSER] WARNING: Cookies file does not exist after flush!');
- }
- } catch (error) {
- console.error('[ELECTRON BROWSER] Failed to flush storage data:', error);
- }
- app.quit();
- });
-});
-
-let isQuitting = false;
-
-app.on('before-quit', async (event) => {
- if (isQuitting) return;
-
- // Prevent immediate quit to allow storage flush and cookie sync
- event.preventDefault();
- isQuitting = true;
-
- console.log('[ELECTRON BROWSER] before-quit event triggered');
-
- try {
- const { session } = require('electron');
- const fs = require('fs');
- const path = require('path');
- const userLoginSession = session.fromPartition('persist:user_login');
-
- // Log cookies before flush
- const cookiesBeforeQuit = await userLoginSession.cookies.get({});
- console.log('[ELECTRON BROWSER] Cookies count before quit:', cookiesBeforeQuit.length);
- if (cookiesBeforeQuit.length > 0) {
- console.log('[ELECTRON BROWSER] Cookie domains before quit:', [...new Set(cookiesBeforeQuit.map(c => c.domain))]);
- }
-
- // Flush storage
- console.log('[ELECTRON BROWSER] Flushing storage on quit...');
- await userLoginSession.flushStorageData();
- console.log('[ELECTRON BROWSER] Storage data flushed on quit');
- } catch (error) {
- console.error('[ELECTRON BROWSER] Failed to sync cookies:', error);
- } finally {
- console.log('[ELECTRON BROWSER] Exiting now...');
- // Force quit after sync
- app.exit(0);
- }
-});
-
-app.on('window-all-closed', () => {
- if (!isQuitting) {
- app.quit();
- }
-});
-'''
-
- # Write the Electron script
- with open(electron_script_path, 'w') as f:
- f.write(electron_script_content)
-
- # Find Electron executable
- # Try to use the same Electron version as the main app
electron_cmd = "npx"
electron_args = [
electron_cmd,
@@ -775,7 +378,9 @@ app.on('window-all-closed', () => {
cwd=app_dir,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # Redirect stderr to stdout
- universal_newlines=True,
+ text=True,
+ encoding='utf-8',
+ errors='replace', # Replace undecodable chars instead of crashing
bufsize=1 # Line buffered
)
@@ -791,17 +396,7 @@ app.on('window-all-closed', () => {
# Wait a bit for Electron to start
import asyncio
await asyncio.sleep(3)
-
- # Clean up the script file after a delay
- async def cleanup_script():
- await asyncio.sleep(10)
- try:
- os.remove(electron_script_path)
- except:
- pass
-
- asyncio.create_task(cleanup_script())
-
+
logger.info(f"[PROFILE USER LOGIN] Electron browser launched with PID {process.pid}")
return {
diff --git a/backend/app/service/chat_service.py b/backend/app/service/chat_service.py
index 404ab646..b9a95f82 100644
--- a/backend/app/service/chat_service.py
+++ b/backend/app/service/chat_service.py
@@ -517,7 +517,14 @@ 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)
+ # Also update camel_task.subtasks to remove deleted tasks (used by to_sub_tasks)
+ update_sub_tasks(camel_task.subtasks, update_tasks)
+ # 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)
+ # Save updated sub_tasks back to task_lock so Action.start uses the correct list
+ setattr(task_lock, "decompose_sub_tasks", sub_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:
@@ -1103,15 +1110,18 @@ def update_sub_tasks(sub_tasks: list[Task], update_tasks: dict[str, TaskContent]
return sub_tasks
-def add_sub_tasks(camel_task: Task, update_tasks: list[TaskContent]):
+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."""
+ added_tasks = []
for item in update_tasks:
- if item.id == "": #
- camel_task.add_subtask(
- Task(
- content=item.content,
- id=f"{camel_task.id}.{len(camel_task.subtasks) + 1}",
- )
+ if item.id == "":
+ 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)
+ return added_tasks
async def question_confirm(agent: ListenChatAgent, prompt: str, task_lock: TaskLock | None = None) -> bool:
diff --git a/backend/app/utils/toolkit/terminal_toolkit.py b/backend/app/utils/toolkit/terminal_toolkit.py
index 9109dcab..659decd5 100644
--- a/backend/app/utils/toolkit/terminal_toolkit.py
+++ b/backend/app/utils/toolkit/terminal_toolkit.py
@@ -169,7 +169,7 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit):
source_cfg = os.path.join(source_venv, "pyvenv.cfg")
python_home = None
- with open(source_cfg, 'r') as f:
+ with open(source_cfg, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('home = '):
python_home = line.split('=', 1)[1].strip()
diff --git a/backend/app/utils/workforce.py b/backend/app/utils/workforce.py
index 363e6462..9579bec2 100644
--- a/backend/app/utils/workforce.py
+++ b/backend/app/utils/workforce.py
@@ -129,6 +129,9 @@ class Workforce(BaseWorkforce):
logger.debug(f"[WF-LIFECYCLE] eigent_start called with {len(subtasks)} subtasks", extra={
"api_task_id": self.api_task_id
})
+ # Clear existing pending tasks to use the user-edited task list
+ # (tasks may have been added during decomposition before user edits)
+ self._pending_tasks.clear()
self._pending_tasks.extendleft(reversed(subtasks))
self.save_snapshot("Initial task decomposition")
diff --git a/electron/main/index.ts b/electron/main/index.ts
index 7b3c1d45..041e8975 100644
--- a/electron/main/index.ts
+++ b/electron/main/index.ts
@@ -1275,6 +1275,7 @@ async function createWindow() {
minWidth: 1050,
minHeight: 650,
frame: false,
+ show: false, // Don't show until content is ready to avoid white screen
transparent: true,
backgroundColor: '#00000000',
titleBarStyle: isMac ? 'hidden' : undefined,
@@ -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/package.json b/package.json
index b1ffee24..bf47fee7 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,7 @@
"csv-parser": "^3.2.0",
"dompurify": "^3.2.7",
"electron-log": "^5.4.0",
- "electron-updater": "^6.7.3",
+ "electron-updater": "^6.3.9",
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.17.0",
@@ -118,11 +118,11 @@
"@vitejs/plugin-react": "^4.3.3",
"@vitest/coverage-v8": "^2.1.9",
"autoprefixer": "^10.4.20",
- "electron": "^33.4.11",
- "electron-builder": "^26.4.0",
+ "electron": "^33.2.0",
+ "electron-builder": "^24.13.3",
"electron-devtools-installer": "^4.0.0",
"i18next": "^25.4.2",
- "jsdom": "^27.4.0",
+ "jsdom": "^26.1.0",
"postcss": "^8.4.49",
"postcss-import": "^16.1.0",
"react": "^18.3.1",
@@ -140,16 +140,10 @@
"@storybook/addon-a11y": "^10.1.11",
"@storybook/addon-docs": "^10.1.11"
},
- "overrides": {
- "glob": "^10.4.5"
- },
"pnpm": {
- "neverBuiltDependencies": [],
- "overrides": {
- "glob": "^10.4.5"
- }
+ "neverBuiltDependencies": []
},
"engines": {
- "node": ">=20.0.0 <23.0.0"
+ "node": ">=18.0.0 <23.0.0"
}
}
diff --git a/src/store/chatStore.ts b/src/store/chatStore.ts
index f44e2639..04872073 100644
--- a/src/store/chatStore.ts
+++ b/src/store/chatStore.ts
@@ -870,6 +870,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';
@@ -2171,20 +2178,14 @@ 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 !== '')
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
+ setTaskRunning(taskId, taskInfo.map(task => ({ ...task })))
- 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) {
@@ -2196,6 +2197,16 @@ const chatStore = (initial?: Partial) => createStore()(
setMessages(taskId, messages)
}
+ if (!type) {
+ 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);
},
@@ -2348,10 +2359,10 @@ const chatStore = (initial?: Partial) => createStore()(
updateTaskInfo(index: number, content: string) {
const { tasks, activeTaskId, setTaskInfo } = get()
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
+ )
setTaskInfo(activeTaskId, targetTaskInfo)
},
deleteTaskInfo(index: number) {