Merge branch 'main' into feature/streaming-agent-output

This commit is contained in:
MkDev11 2026-01-22 08:49:58 -05:00 committed by GitHub
commit d1e0e99356
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 504 additions and 450 deletions

View file

@ -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 = `
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
#nav-bar {
display: flex;
align-items: center;
padding: 8px;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
gap: 8px;
}
button {
padding: 6px 12px;
border: 1px solid #ccc;
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 4px;
}
button:hover:not(:disabled) {
background: #f0f0f0;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#url-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
#url-input:focus {
outline: none;
border-color: #4285f4;
}
#webview {
flex: 1;
width: 100%;
border: none;
}
.nav-icon {
font-size: 16px;
}
#loading-indicator {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #4285f4;
border-radius: 50%;
animation: spin 1s linear infinite;
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading #loading-indicator {
display: block;
}
.loading #reload-btn .nav-icon {
display: none;
}
</style>
</head>
<body>
<div id="nav-bar">
<button id="back-btn" title="Back">
<span class="nav-icon">&#x2190;</span>
</button>
<button id="forward-btn" title="Forward">
<span class="nav-icon">&#x2192;</span>
</button>
<button id="reload-btn" title="Reload">
<span class="nav-icon">&#x21bb;</span>
<div id="loading-indicator"></div>
</button>
<button id="home-btn" title="Home">
<span class="nav-icon">&#x1F3E0;</span>
</button>
<input type="text" id="url-input" placeholder="Enter URL..." />
<button id="go-btn">Go</button>
<button id="linkedin-btn" style="background: #0077B5; color: white; border-color: #0077B5;">
LinkedIn
</button>
<button id="info-btn" title="Show Info">&#x2139;&#xFE0F;</button>
</div>
<webview id="webview" src="${startUrl}" partition="persist:user_login"></webview>
<div id="info-panel" style="display: none; position: absolute; top: 50px; right: 10px; background: white; border: 1px solid #ccc; padding: 15px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 1000; max-width: 400px; font-size: 12px;">
<h4 style="margin: 0 0 10px 0;">Browser Info</h4>
<div id="info-content"></div>
<button onclick="document.getElementById('info-panel').style.display='none'" style="margin-top: 10px;">Close</button>
</div>
<script>
const webview = document.getElementById('webview');
const backBtn = document.getElementById('back-btn');
const forwardBtn = document.getElementById('forward-btn');
const reloadBtn = document.getElementById('reload-btn');
const homeBtn = document.getElementById('home-btn');
const urlInput = document.getElementById('url-input');
const goBtn = document.getElementById('go-btn');
const linkedinBtn = document.getElementById('linkedin-btn');
const navBar = document.getElementById('nav-bar');
const infoBtn = document.getElementById('info-btn');
const infoPanel = document.getElementById('info-panel');
const infoContent = document.getElementById('info-content');
// Show info panel
infoBtn.addEventListener('click', () => {
const { ipcRenderer } = require('electron');
// Get browser info
const info = {
'Chrome Version': process.versions.chrome,
'Electron Version': process.versions.electron,
'Node Version': process.versions.node,
'User Data Dir (requested)': '${userDataDir}',
'CDP Port': '${cdpPort}',
'Platform': process.platform,
'Architecture': process.arch
};
// Also check webview partition info
const partition = webview.partition || 'default';
info['WebView Partition'] = partition;
// Format info as HTML
let html = '<table style="width: 100%; border-collapse: collapse;">';
for (const [key, value] of Object.entries(info)) {
html += '<tr><td style="padding: 4px; border-bottom: 1px solid #eee;"><strong>' + key + ':</strong></td><td style="padding: 4px; border-bottom: 1px solid #eee; word-break: break-all;">' + value + '</td></tr>';
}
html += '</table>';
infoContent.innerHTML = html;
infoPanel.style.display = 'block';
});
// Update navigation buttons
function updateNavButtons() {
backBtn.disabled = !webview.canGoBack();
forwardBtn.disabled = !webview.canGoForward();
}
// Navigate to URL
function navigateToUrl() {
let url = urlInput.value.trim();
if (!url) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'https://' + url;
}
webview.loadURL(url);
}
// Event listeners
backBtn.addEventListener('click', () => webview.goBack());
forwardBtn.addEventListener('click', () => webview.goForward());
reloadBtn.addEventListener('click', () => webview.reload());
homeBtn.addEventListener('click', () => webview.loadURL('${startUrl}'));
goBtn.addEventListener('click', navigateToUrl);
linkedinBtn.addEventListener('click', () => webview.loadURL('https://www.linkedin.com'));
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
navigateToUrl();
}
});
// WebView events
webview.addEventListener('did-start-loading', () => {
navBar.classList.add('loading');
});
webview.addEventListener('did-stop-loading', () => {
navBar.classList.remove('loading');
updateNavButtons();
});
webview.addEventListener('did-navigate', (e) => {
urlInput.value = e.url;
updateNavButtons();
});
webview.addEventListener('did-navigate-in-page', (e) => {
urlInput.value = e.url;
updateNavButtons();
});
webview.addEventListener('new-window', (e) => {
// Open new windows in the same webview
e.preventDefault();
webview.loadURL(e.url);
});
// Initialize
updateNavButtons();
</script>
</body>
</html>`;
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();
}
});

View file

@ -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 = `
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
#nav-bar {
display: flex;
align-items: center;
padding: 8px;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
gap: 8px;
}
button {
padding: 6px 12px;
border: 1px solid #ccc;
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 4px;
}
button:hover:not(:disabled) {
background: #f0f0f0;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#url-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
#url-input:focus {
outline: none;
border-color: #4285f4;
}
#webview {
flex: 1;
width: 100%;
border: none;
}
.nav-icon {
font-size: 16px;
}
#loading-indicator {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #4285f4;
border-radius: 50%;
animation: spin 1s linear infinite;
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading #loading-indicator {
display: block;
}
.loading #reload-btn .nav-icon {
display: none;
}
</style>
</head>
<body>
<div id="nav-bar">
<button id="back-btn" title="Back">
<span class="nav-icon"></span>
</button>
<button id="forward-btn" title="Forward">
<span class="nav-icon"></span>
</button>
<button id="reload-btn" title="Reload">
<span class="nav-icon"></span>
<div id="loading-indicator"></div>
</button>
<button id="home-btn" title="Home">
<span class="nav-icon">🏠</span>
</button>
<input type="text" id="url-input" placeholder="Enter URL..." />
<button id="go-btn">Go</button>
<button id="linkedin-btn" style="background: #0077B5; color: white; border-color: #0077B5;">
LinkedIn
</button>
<button id="info-btn" title="Show Info"></button>
</div>
<webview id="webview" src="${startUrl}" partition="persist:user_login"></webview>
<div id="info-panel" style="display: none; position: absolute; top: 50px; right: 10px; background: white; border: 1px solid #ccc; padding: 15px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 1000; max-width: 400px; font-size: 12px;">
<h4 style="margin: 0 0 10px 0;">Browser Info</h4>
<div id="info-content"></div>
<button onclick="document.getElementById('info-panel').style.display='none'" style="margin-top: 10px;">Close</button>
</div>
<script>
const webview = document.getElementById('webview');
const backBtn = document.getElementById('back-btn');
const forwardBtn = document.getElementById('forward-btn');
const reloadBtn = document.getElementById('reload-btn');
const homeBtn = document.getElementById('home-btn');
const urlInput = document.getElementById('url-input');
const goBtn = document.getElementById('go-btn');
const linkedinBtn = document.getElementById('linkedin-btn');
const navBar = document.getElementById('nav-bar');
const infoBtn = document.getElementById('info-btn');
const infoPanel = document.getElementById('info-panel');
const infoContent = document.getElementById('info-content');
// Show info panel
infoBtn.addEventListener('click', () => {
const { ipcRenderer } = require('electron');
// Get browser info
const info = {
'Chrome Version': process.versions.chrome,
'Electron Version': process.versions.electron,
'Node Version': process.versions.node,
'User Data Dir (requested)': '${userDataDir}',
'CDP Port': '${cdpPort}',
'Platform': process.platform,
'Architecture': process.arch
};
// Also check webview partition info
const partition = webview.partition || 'default';
info['WebView Partition'] = partition;
// Format info as HTML
let html = '<table style="width: 100%; border-collapse: collapse;">';
for (const [key, value] of Object.entries(info)) {
html += '<tr><td style="padding: 4px; border-bottom: 1px solid #eee;"><strong>' + key + ':</strong></td><td style="padding: 4px; border-bottom: 1px solid #eee; word-break: break-all;">' + value + '</td></tr>';
}
html += '</table>';
infoContent.innerHTML = html;
infoPanel.style.display = 'block';
});
// Update navigation buttons
function updateNavButtons() {
backBtn.disabled = !webview.canGoBack();
forwardBtn.disabled = !webview.canGoForward();
}
// Navigate to URL
function navigateToUrl() {
let url = urlInput.value.trim();
if (!url) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'https://' + url;
}
webview.loadURL(url);
}
// Event listeners
backBtn.addEventListener('click', () => webview.goBack());
forwardBtn.addEventListener('click', () => webview.goForward());
reloadBtn.addEventListener('click', () => webview.reload());
homeBtn.addEventListener('click', () => webview.loadURL('${startUrl}'));
goBtn.addEventListener('click', navigateToUrl);
linkedinBtn.addEventListener('click', () => webview.loadURL('https://www.linkedin.com'));
urlInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
navigateToUrl();
}
});
// WebView events
webview.addEventListener('did-start-loading', () => {
navBar.classList.add('loading');
});
webview.addEventListener('did-stop-loading', () => {
navBar.classList.remove('loading');
updateNavButtons();
});
webview.addEventListener('did-navigate', (e) => {
urlInput.value = e.url;
updateNavButtons();
});
webview.addEventListener('did-navigate-in-page', (e) => {
urlInput.value = e.url;
updateNavButtons();
});
webview.addEventListener('new-window', (e) => {
// Open new windows in the same webview
e.preventDefault();
webview.loadURL(e.url);
});
// Initialize
updateNavButtons();
</script>
</body>
</html>`;
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 {

View file

@ -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:

View file

@ -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()

View file

@ -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")

View file

@ -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<void>((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...');

View file

@ -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"
}
}

View file

@ -870,6 +870,13 @@ const chatStore = (initial?: Partial<ChatStore>) => createStore<ChatStore>()(
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<ChatStore>) => createStore<ChatStore>()(
// 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<ChatStore>) => createStore<ChatStore>()(
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<ChatStore>) => createStore<ChatStore>()(
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) {