Skip to content
Open
Show file tree
Hide file tree
Changes from 86 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
f768d54
project side bar and new layout design
Douglasymlai Mar 24, 2026
fdb1fbb
Merge branch 'main' into project-side-bar
4pmtong Mar 25, 2026
3f3447a
udpate side bar design
Douglasymlai Mar 26, 2026
d21f062
update content
Douglasymlai Mar 26, 2026
9fb75a1
move single agent chatbox ui update to sidebar version (#1522)
Douglasymlai Mar 27, 2026
cb87108
feature all updated
Douglasymlai Mar 27, 2026
dfa4c5d
update language
Douglasymlai Mar 27, 2026
12cf809
review and fix bug for hardcoded state
Douglasymlai Mar 27, 2026
c0bad6a
iteration change for the workforce window interaction
Douglasymlai Mar 27, 2026
ae2c08e
restrucutre components
Douglasymlai Mar 27, 2026
88b68d7
Merge branch 'main' into feat-new-project-workspace-with-sidebar
Douglasymlai Mar 27, 2026
8451cc5
fix model missing notification layout
Douglasymlai Mar 27, 2026
e3ee804
revert background color for visibility
Douglasymlai Mar 27, 2026
72ddd22
update bug fix for texting, and missing margins
Douglasymlai Mar 29, 2026
87693f6
check language translation
Douglasymlai Mar 30, 2026
d1c86d5
bug fix
Douglasymlai Mar 30, 2026
47dfd4d
ux improvement for starting new project
Douglasymlai Mar 31, 2026
bfd6baf
initial push
Douglasymlai Mar 31, 2026
2faf2ce
Merge branch 'main' into feat-new-project-workspace-with-sidebar
4pmtong Apr 1, 2026
61a122f
bug fix to remove confusing feature
Douglasymlai Apr 7, 2026
7e9fa85
Merge branch 'feat-new-project-workspace-with-sidebar' into feat-new-…
Douglasymlai Apr 8, 2026
72e93bc
Merge origin/main into feat-new-workspace-ai-worker
Douglasymlai Apr 9, 2026
19e6ee1
resolve conflicts
Douglasymlai Apr 9, 2026
086df14
redesign for chathistory and workspace panel
Douglasymlai Apr 9, 2026
c852c3e
add all new features
Douglasymlai Apr 17, 2026
8468684
update design
Douglasymlai Apr 17, 2026
b8204f2
fix import bug
Douglasymlai Apr 17, 2026
bb5daa2
bug fix
Douglasymlai Apr 18, 2026
d3810f9
chore(ui): reorganize component paths and shared layout
Douglasymlai Apr 18, 2026
d604be3
Design Roken refactor (#1569)
Douglasymlai Apr 21, 2026
c734699
Refactor chat rendering (#1570)
Douglasymlai Apr 21, 2026
cd8fcce
Feat/home workspace UI polish 20260422 (#1572)
Douglasymlai Apr 22, 2026
65486c6
style fix for folder content
Douglasymlai Apr 22, 2026
273ce4f
update design change
Douglasymlai Apr 22, 2026
46f9355
Refactor agent log (#1574)
Douglasymlai Apr 22, 2026
0988edd
Merge branch 'refactor_eigent' into feat/eigent-new-design
Douglasymlai Apr 22, 2026
7402171
Merge origin/refactor_eigent: resolve TopBar, Models, Setting, WorkFl…
Douglasymlai Apr 22, 2026
c35396f
edit default app size
Douglasymlai Apr 22, 2026
024133a
feat(ui): add formFieldSurface and share form control chrome
Douglasymlai Apr 23, 2026
047a028
feat(ui): add Alert tone API and deprecate destructive variant
Douglasymlai Apr 23, 2026
fe6b397
refactor(ui): rename TabsList/TabsTrigger variant to appearance
Douglasymlai Apr 23, 2026
9c4766c
refactor(ui): add tone to menu-button and resolve look from variant
Douglasymlai Apr 23, 2026
e20284b
fix(ui): merge Toggle className with cn() outside toggleVariants
Douglasymlai Apr 23, 2026
bda0e29
style(ui): use design token for Skeleton background
Douglasymlai Apr 23, 2026
13ea060
chore(storybook): centralize UI stories under src/stories/ui
Douglasymlai Apr 23, 2026
a96f100
refactor(ui): use Tailwind ds-* tokens in sheet, tabs, and sidebar
Douglasymlai Apr 23, 2026
8106c9a
refactor(background): use Tailwind ds-* on SVG patterns and lottie ba…
Douglasymlai Apr 23, 2026
31bcf19
refactor(chat): use ds-* Tailwind utilities in message components
Douglasymlai Apr 23, 2026
ca086ab
refactor(chat): use ds-* utilities in input area and user query group
Douglasymlai Apr 23, 2026
172936c
refactor(chat): use ds-* utilities in task list and session timeline
Douglasymlai Apr 23, 2026
48e94c1
refactor(layout): use ds-* utilities in top bar, history search, proj…
Douglasymlai Apr 23, 2026
070339b
refactor(workflow): use border-ds-* for markdown tables
Douglasymlai Apr 23, 2026
10f14ea
refactor(settings): use accent-ds-* for appearance range input
Douglasymlai Apr 23, 2026
17f8f61
feat(theme): dialog overlay scrim token and mode-split aliases
Douglasymlai Apr 23, 2026
33f76c7
feat(ui): dialog overlay variants and scrim on alert
Douglasymlai Apr 23, 2026
69af431
feat(ui): use dialog overlay scrim on sheets and full-screen backdrops
Douglasymlai Apr 23, 2026
b589f3c
refactor(ui): prefer design tokens over raw colors
Douglasymlai Apr 23, 2026
aa888e5
chore: add design-token usage guardrail and allowlist
Douglasymlai Apr 23, 2026
0070655
Tighten auth and install step padding, soften card background
Douglasymlai Apr 23, 2026
421f01c
feat(sidebar): rich session list lead icons from task lifecycle
Douglasymlai Apr 23, 2026
d38baee
chore(deps): alphabetize @emotion/is-prop-valid in dependencies
Douglasymlai Apr 23, 2026
debd59b
feat(history): Blocks icon for projects tab; align padding; task titl…
Douglasymlai Apr 23, 2026
bc9416c
refactor(topbar): useHost for Electron APIs; simplify history header
Douglasymlai Apr 23, 2026
a85e3c4
feat(settings): version and update actions in sidebar
Douglasymlai Apr 23, 2026
c08386f
style(models): design tokens and controls on Models settings
Douglasymlai Apr 23, 2026
5d2cbd6
chore(assets): add integration icons and update Slack icon
Douglasymlai Apr 23, 2026
f285deb
feat(i18n): add MCP and connectors strings for settings
Douglasymlai Apr 23, 2026
1d28863
feat(connectors): expand MCP UI and connectors entry
Douglasymlai Apr 23, 2026
c5ad335
style change for skills and dashboard panel
Douglasymlai Apr 23, 2026
ae753dc
refactor(Workspace): use Host abstraction for Electron APIs
Douglasymlai Apr 23, 2026
43ee2b7
refactor(ChatBox): use Host abstraction for Electron APIs
Douglasymlai Apr 23, 2026
4582fcb
refactor(Workforce): use Host abstraction for webview and overlay
Douglasymlai Apr 23, 2026
92dbb06
refactor(settings): use Host abstraction for proxy and restart
Douglasymlai Apr 23, 2026
175e62a
feat(report-bug): add diagnostics export and email report flow
Douglasymlai Apr 23, 2026
9cb2b12
refactor(session-nav): unify session rows across workspace views
Douglasymlai Apr 23, 2026
4a7e9c2
feat(history-ui): refresh token badges and history header labels
Douglasymlai Apr 23, 2026
7d7fccf
fix(theme-tokens): pin dark subtle status backgrounds where required
Douglasymlai Apr 23, 2026
b3c00ca
style(ui): update shared form and surface primitives
Douglasymlai Apr 23, 2026
49f3b16
feat(add-worker): refresh tool selection UI and agent tool copy
Douglasymlai Apr 23, 2026
3f840ff
feat(app): update workspace shell, nav, and integrations
Douglasymlai Apr 23, 2026
fd129b3
fix: correct BrowserAgentWorkspace import path casing for CI and TS1261
Douglasymlai Apr 23, 2026
255b94f
chore(theme): update preset ids and add whale and custom in base colo…
Douglasymlai Apr 23, 2026
877b1ae
feat(ui): add ruled, dotted, and dashed line background overlays
Douglasymlai Apr 23, 2026
3aea8c7
feat(settings): add workspace line backgrounds and persist choice
Douglasymlai Apr 23, 2026
f4f5729
git commit -m "style: fix ruff import order in browser factory"
4pmtong Apr 24, 2026
b03f105
local new design refactor fixes (#1581)
4pmtong Apr 27, 2026
40ce28c
fix: preserve brain browser architecture in new design
4pmtong Apr 27, 2026
8066421
fix: preserve brain browser architecture in new design
4pmtong Apr 27, 2026
f30b30a
fix: improve folder file downloads
Douglasymlai Apr 27, 2026
f45967c
fix: collect side panel output files from all task sources
Douglasymlai Apr 27, 2026
07a799d
fix: use default i18n namespace for preparing-to-execute copy
Douglasymlai Apr 27, 2026
1757963
style: increase workspace empty state vertical spacing
Douglasymlai Apr 27, 2026
aa8fe79
refactor: align dashboard subpage headers with scroll shell
Douglasymlai Apr 27, 2026
70dd142
refactor: restyle history home inside padded shell
Douglasymlai Apr 27, 2026
0e80a45
feat: move project controls and end flow into the top bar
Douglasymlai Apr 27, 2026
2b6dc99
refactor: simplify notification panel and add empty state copy
Douglasymlai Apr 27, 2026
420ab5b
fix: adjust macOS top bar trailing cluster padding
Douglasymlai Apr 27, 2026
995e07c
fix: improve task work log active row display
Douglasymlai Apr 27, 2026
c2f8152
refactor: update streaming task row styling
Douglasymlai Apr 27, 2026
dfad3b5
fix(agents): align Models page Button props with design system
Douglasymlai Apr 28, 2026
1f6f061
refactor(ui): unify Lucide stroke styling
Douglasymlai Apr 28, 2026
7b05ac5
fix: restore replay task elapsed time (#1582)
Douglasymlai Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ jobs:
- name: Check Electron Access Guard
run: bash scripts/check-electron-access.sh

- name: Design tokens (engine + no hard-coded colors in UI)
run: npm run check:design-tokens

pytest:
name: Run Python Tests
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ node-compile-cache

node_modules
dist
dist-web
!package/**/dist
dist-ssr
dist-electron
Expand Down
1 change: 1 addition & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"*.{ts,tsx}": [
"eslint --fix --no-warn-ignored",
"node scripts/check-design-token-usage.mjs",
"prettier --write",
"node licenses/update_license.js"
],
Expand Down
2 changes: 1 addition & 1 deletion backend/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
if request.url.path.startswith("/files/preview/"):
if "/files/preview/" in request.url.path:
if "X-Frame-Options" in response.headers:
del response.headers["X-Frame-Options"]
response.headers["Content-Security-Policy"] = (
Expand Down
11 changes: 7 additions & 4 deletions backend/app/controller/chat_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@
task_locks,
)
from app.utils.browser_launcher import (
ensure_cdp_browser_available,
ensure_cdp_browser_endpoint,
is_cdp_url_available,
normalize_cdp_url,
)

router = APIRouter()
Expand Down Expand Up @@ -109,7 +110,7 @@ async def _prepare_browser_for_request(
return True

try:
launched = await asyncio.to_thread(ensure_cdp_browser_available, port)
endpoint = await asyncio.to_thread(ensure_cdp_browser_endpoint, port)
except Exception as e:
os.environ.pop("EIGENT_CDP_URL", None)
chat_logger.warning(
Expand All @@ -120,8 +121,10 @@ async def _prepare_browser_for_request(
request.state.browser_available = False
return False

if launched:
os.environ["EIGENT_CDP_URL"] = f"http://127.0.0.1:{port}"
if endpoint:
os.environ["EIGENT_CDP_URL"] = endpoint
_, _, selected_port = normalize_cdp_url(endpoint)
os.environ["browser_port"] = str(selected_port)
if request is not None:
request.state.browser_available = True
return True
Expand Down
66 changes: 42 additions & 24 deletions backend/app/controller/tool_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
DEFAULT_CDP_PORT,
_is_cdp_available,
_is_port_in_use,
ensure_cdp_browser_available,
ensure_cdp_browser_endpoint,
is_local_cdp_host,
normalize_cdp_url,
)
Expand All @@ -50,6 +50,7 @@ class LinkedInTokenRequest(BaseModel):
logger = logging.getLogger("tool_controller")
router = APIRouter()
_web_cdp_browser_meta: dict | None = None
DEFAULT_LOGIN_BROWSER_CDP_PORT = 9323


class CdpBrowserConnectRequest(BaseModel):
Expand Down Expand Up @@ -114,6 +115,37 @@ def _get_connected_cdp_port() -> int | None:
return None


def _get_login_browser_cdp_port() -> int:
"""Dedicated CDP port for the user-login cookie browser.

Keep this outside the Browser Agent fallback range (9223-9299), otherwise
Cookie Management can mistake a managed task browser for the login window.
"""
raw_port = os.environ.get("EIGENT_LOGIN_BROWSER_CDP_PORT")
if not raw_port:
return DEFAULT_LOGIN_BROWSER_CDP_PORT

try:
port = int(raw_port)
except ValueError:
logger.warning(
"Invalid EIGENT_LOGIN_BROWSER_CDP_PORT=%s; using default %s",
raw_port,
DEFAULT_LOGIN_BROWSER_CDP_PORT,
)
return DEFAULT_LOGIN_BROWSER_CDP_PORT

if port <= 0 or port > 65535:
logger.warning(
"Out-of-range EIGENT_LOGIN_BROWSER_CDP_PORT=%s; using default %s",
raw_port,
DEFAULT_LOGIN_BROWSER_CDP_PORT,
)
return DEFAULT_LOGIN_BROWSER_CDP_PORT

return port


def _set_connected_cdp_browser(
endpoint: str,
*,
Expand Down Expand Up @@ -271,21 +303,20 @@ async def launch_cdp_browser(request: Request):
"endpoint": browser.get("endpoint"),
}

port = DEFAULT_CDP_PORT
launched = ensure_cdp_browser_available(port)
if not launched:
if _is_port_in_use(port):
endpoint = ensure_cdp_browser_endpoint(DEFAULT_CDP_PORT)
if not endpoint:
if _is_port_in_use(DEFAULT_CDP_PORT):
return {
"success": False,
"error": f"Port {port} is already in use and is not exposing CDP.",
"error": f"Port {DEFAULT_CDP_PORT} is already in use and is not exposing a compatible CDP browser.",
}
return {
"success": False,
"error": "Failed to launch browser. Ensure Chrome/Chromium is installed or run playwright install chromium.",
}

browser = _set_connected_cdp_browser(
f"http://127.0.0.1:{port}",
endpoint,
is_external=False,
)
return {
Expand Down Expand Up @@ -970,12 +1001,11 @@ async def open_browser_login():
Browser session information
"""
try:
import socket
import subprocess

# Use fixed profile name for persistent logins (no port suffix)
session_id = "user_login"
cdp_port = 9223
cdp_port = _get_login_browser_cdp_port()

# IMPORTANT: Use dedicated profile for tool_controller browser
# This is the SOURCE OF TRUTH for login data
Expand All @@ -996,12 +1026,7 @@ async def open_browser_login():
f" at: {user_data_dir}"
)

# Check if browser is already running on this port
def is_port_in_use(port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("localhost", port)) == 0

if is_port_in_use(cdp_port):
if _is_port_in_use(cdp_port):
logger.info(f"Browser already running on port {cdp_port}")
return {
"success": True,
Expand Down Expand Up @@ -1135,15 +1160,8 @@ def log_electron_output():
@router.get("/browser/status", name="browser status")
async def browser_status():
"""Check if the login browser is currently open."""
import socket

cdp_port = 9223

def is_port_in_use(port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("localhost", port)) == 0

return {"is_open": is_port_in_use(cdp_port)}
cdp_port = _get_login_browser_cdp_port()
return {"is_open": _is_port_in_use(cdp_port), "cdp_port": cdp_port}


@router.get("/browser/cookies", name="list cookie domains")
Expand Down
129 changes: 126 additions & 3 deletions backend/app/utils/browser_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
In web mode, Brain launches Chrome/Chromium directly.
"""

import json
import logging
import os
import platform
Expand All @@ -32,6 +33,8 @@

# Default CDP port (must match browser_port in Chat model)
DEFAULT_CDP_PORT = 9222
FALLBACK_CDP_PORT_START = 9223
FALLBACK_CDP_PORT_END = 9299
LOCAL_CDP_HOSTS = frozenset({"127.0.0.1", "localhost", "::1"})


Expand Down Expand Up @@ -66,7 +69,9 @@ def is_cdp_url_available(cdp_url: str) -> bool:
import httpx

r = httpx.get(f"{normalized}/json/version", timeout=2.0)
return r.status_code == 200
if r.status_code != 200:
return False
return _is_supported_cdp_version(r.json(), normalized)
except Exception:
return False

Expand All @@ -78,16 +83,101 @@ def _is_port_in_use(port: int) -> bool:


def _is_cdp_available(port: int) -> bool:
"""Check if a CDP-capable browser is listening on the port."""
"""Check if a Playwright-compatible CDP browser is listening."""
try:
import httpx

r = httpx.get(f"http://127.0.0.1:{port}/json/version", timeout=2.0)
return r.status_code == 200
if r.status_code != 200:
return False
return _is_supported_cdp_version(r.json(), f"http://127.0.0.1:{port}")
except Exception:
return False


def _is_supported_cdp_version(data: dict, endpoint: str) -> bool:
"""Reject CDP endpoints that Playwright cannot manage."""
browser = str(data.get("Browser") or "")
user_agent = str(data.get("User-Agent") or "")
websocket_url = data.get("webSocketDebuggerUrl")
combined = f"{browser} {user_agent}"

if not websocket_url:
logger.debug(
"[BROWSER LAUNCHER] CDP endpoint has no browser websocket"
)
return False

if "Electron" in combined:
logger.warning(
"[BROWSER LAUNCHER] Ignoring Electron DevTools endpoint at %s; "
"Browser Agent requires a managed Chrome/Chromium CDP browser.",
endpoint,
)
return False

if not any(
token in combined
for token in ("Chrome/", "Chromium", "HeadlessChrome/")
):
logger.warning(
"[BROWSER LAUNCHER] Ignoring unsupported CDP endpoint at %s: %s",
endpoint,
browser or user_agent or "unknown browser",
)
return False

if not _supports_browser_context_management(str(websocket_url), endpoint):
return False

return True


def _supports_browser_context_management(
websocket_url: str,
endpoint: str,
) -> bool:
"""Probe the browser-level CDP socket for Playwright compatibility."""
try:
from websockets.sync.client import connect

command = {
"id": 1,
"method": "Browser.setDownloadBehavior",
"params": {"behavior": "default"},
}
with connect(websocket_url, open_timeout=2, close_timeout=1) as ws:
ws.send(json.dumps(command))
response = json.loads(ws.recv(timeout=2))
except Exception as exc:
logger.warning(
"[BROWSER LAUNCHER] Could not validate CDP capabilities at %s: %s",
endpoint,
exc,
)
return False

error = response.get("error")
if error:
message = error.get("message") if isinstance(error, dict) else error
logger.warning(
"[BROWSER LAUNCHER] Ignoring CDP endpoint at %s; "
"it does not support browser context management: %s",
endpoint,
message,
)
return False

return True


def _candidate_ports(preferred_port: int):
yield preferred_port
for port in range(FALLBACK_CDP_PORT_START, FALLBACK_CDP_PORT_END + 1):
if port != preferred_port:
yield port


def _find_chrome_executable() -> str | None:
"""Find Chrome or Chromium executable for launching with CDP."""
system = platform.system()
Expand Down Expand Up @@ -252,3 +342,36 @@ def ensure_cdp_browser_available(port: int = DEFAULT_CDP_PORT) -> bool:
"[BROWSER LAUNCHER] Browser launched but CDP not ready after 10s"
)
return False


def ensure_cdp_browser_endpoint(
preferred_port: int = DEFAULT_CDP_PORT,
) -> str | None:
"""
Ensure a managed CDP browser exists and return its endpoint.

If the preferred port is occupied by Electron's own DevTools endpoint, use
the next available local port instead of handing that endpoint to
Playwright.
"""
for port in _candidate_ports(preferred_port):
if _is_cdp_available(port):
return f"http://127.0.0.1:{port}"

if _is_port_in_use(port):
logger.warning(
"[BROWSER LAUNCHER] Port %s is occupied by a non-managed "
"or unsupported CDP endpoint; trying another port.",
port,
)
continue

if ensure_cdp_browser_available(port):
return f"http://127.0.0.1:{port}"

logger.error(
"[BROWSER LAUNCHER] No available CDP browser port in %s-%s",
preferred_port,
FALLBACK_CDP_PORT_END,
)
return None
Loading
Loading