diff --git a/backend/app/controller/tool_controller.py b/backend/app/controller/tool_controller.py index 209f6bbda..29320da59 100644 --- a/backend/app/controller/tool_controller.py +++ b/backend/app/controller/tool_controller.py @@ -260,11 +260,19 @@ async def uninstall_tool(tool: str): elif tool == "google_calendar": try: - # Clean up Google Calendar token directory - token_dir = os.path.join(os.path.expanduser("~"), ".eigent", "tokens", "google_calendar") - if os.path.exists(token_dir): - shutil.rmtree(token_dir) - logger.info(f"Removed Google Calendar token directory: {token_dir}") + # Clean up Google Calendar token directories (user-scoped + legacy) + token_dirs = set() + try: + token_dirs.add(os.path.dirname(GoogleCalendarToolkit._build_canonical_token_path())) + except Exception as e: + logger.warning(f"Failed to resolve canonical Google Calendar token path: {e}") + + token_dirs.add(os.path.join(os.path.expanduser("~"), ".eigent", "tokens", "google_calendar")) + + for token_dir in token_dirs: + if os.path.exists(token_dir): + shutil.rmtree(token_dir) + logger.info(f"Removed Google Calendar token directory: {token_dir}") # Clear OAuth state manager cache (this is the key fix!) # This removes the cached credentials from memory diff --git a/backend/app/utils/toolkit/google_calendar_toolkit.py b/backend/app/utils/toolkit/google_calendar_toolkit.py index f89d8a175..4cd5c180a 100644 --- a/backend/app/utils/toolkit/google_calendar_toolkit.py +++ b/backend/app/utils/toolkit/google_calendar_toolkit.py @@ -21,18 +21,26 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): def __init__(self, api_task_id: str, timeout: float | None = None): self.api_task_id = api_task_id - self._token_path = ( - env("GOOGLE_CALENDAR_TOKEN_PATH") - or os.path.join( - os.path.expanduser("~"), - ".eigent", - "tokens", - "google_calendar", - f"google_calendar_token_{api_task_id}.json", - ) + # Use a stable token file (no per-task suffix). Can be overridden by env. + self._token_path = env("GOOGLE_CALENDAR_TOKEN_PATH") or os.path.join( + os.path.expanduser("~"), + ".eigent", + "tokens", + "google_calendar", + "google_calendar_token.json", ) super().__init__(timeout) + @classmethod + def _build_canonical_token_path(cls) -> str: + return env("GOOGLE_CALENDAR_TOKEN_PATH") or os.path.join( + os.path.expanduser("~"), + ".eigent", + "tokens", + "google_calendar", + "google_calendar_token.json", + ) + @classmethod def get_can_use_tools(cls, api_task_id: str): from dotenv import load_dotenv @@ -77,12 +85,19 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): creds = None - # First, try to load from token file + # First, try to load from token file (canonical then legacy install_auth) try: if os.path.exists(self._token_path): logger.info(f"Loading credentials from token file: {self._token_path}") creds = Credentials.from_authorized_user_file(self._token_path, SCOPES) logger.info("Successfully loaded credentials from token file") + elif os.path.exists(self._token_path.replace("google_calendar_token.json", "google_calendar_token_install_auth.json")): + legacy_path = self._token_path.replace( + "google_calendar_token.json", "google_calendar_token_install_auth.json" + ) + logger.info(f"Loading credentials from legacy token file: {legacy_path}") + creds = Credentials.from_authorized_user_file(legacy_path, SCOPES) + logger.info("Successfully loaded credentials from legacy token file") except Exception as e: logger.warning(f"Could not load from token file: {e}") creds = None @@ -231,12 +246,12 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): return # Save credentials to token file - token_path = os.path.join( + token_path = env("GOOGLE_CALENDAR_TOKEN_PATH") or os.path.join( os.path.expanduser("~"), ".eigent", "tokens", "google_calendar", - f"google_calendar_token_{api_task_id}.json", + "google_calendar_token.json", ) try: @@ -269,4 +284,4 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): thread.start() logger.info("Started background Google Calendar authorization") - return "authorizing" \ No newline at end of file + return "authorizing" diff --git a/electron/main/init.ts b/electron/main/init.ts index b09db6595..39dc641ca 100644 --- a/electron/main/init.ts +++ b/electron/main/init.ts @@ -7,7 +7,7 @@ import * as net from "net"; import * as http from "http"; import { ipcMain, BrowserWindow, app } from 'electron' import { promisify } from 'util' -import { detectInstallationLogs, PromiseReturnType } from "./install-deps"; +import { PromiseReturnType } from "./install-deps"; const execAsync = promisify(exec); @@ -171,8 +171,11 @@ export async function startBackend(setPort?: (port: number) => void): Promise { if (!data) return; const msg = data.toString().trimEnd(); - //Detect if uv sync is run - detectInstallationLogs(msg); + + // REMOVED: detectInstallationLogs(msg) + // Reason: Removed keyword-based detection to avoid false positives when backend + // outputs logs containing keywords like "Installing", "Updating", "Syncing" etc. + // Installation is now only handled through the explicit installation flow. if (msg.toLowerCase().includes("error") || msg.toLowerCase().includes("traceback")) { log.error(`BACKEND: ${msg}`); diff --git a/electron/main/install-deps.ts b/electron/main/install-deps.ts index 257fca38e..7156b1c1f 100644 --- a/electron/main/install-deps.ts +++ b/electron/main/install-deps.ts @@ -677,104 +677,4 @@ export async function installDependencies(version: string): Promise - msg.includes(pattern) && !msg.includes("Uvicorn running on") - )) { - dependencyInstallationDetected = true; - log.info('[BACKEND STARTUP] UV dependency installation detected during uvicorn startup'); - - // Create installing lock file to maintain consistency with install-deps.ts - InstallLogs.setLockPath(); - log.info('[BACKEND STARTUP] Created uv_installing.lock file'); - - // Notify frontend that installation has started (only once) - if (!installationNotificationSent) { - installationNotificationSent = true; - const notificationSent = safeMainWindowSend('install-dependencies-start'); - if (notificationSent) { - log.info('[BACKEND STARTUP] Notified frontend of dependency installation start'); - } else { - log.warn('[BACKEND STARTUP] Failed to notify frontend of dependency installation start'); - } - } - } - - // Send installation logs to frontend if installation was detected - if (dependencyInstallationDetected && !msg.includes("Uvicorn running on")) { - safeMainWindowSend('install-dependencies-log', { - type: msg.toLowerCase().includes("error") || msg.toLowerCase().includes("traceback") ? 'stderr' : 'stdout', - data: msg - }); - } - - // Check if installation is complete (uvicorn starts successfully) - if (dependencyInstallationDetected && msg.includes("Uvicorn running on")) { - log.info('[BACKEND STARTUP] UV dependency installation completed, uvicorn started successfully'); - - // Clean up installing lock and create installed lock - InstallLogs.cleanLockPath(); - fs.writeFileSync(installedLockPath, ''); - log.info('[BACKEND STARTUP] Created uv_installed.lock file'); - - safeMainWindowSend('install-dependencies-complete', { - success: true, - message: 'Dependencies installed successfully during backend startup' - }); - } - - // Handle installation failures - if (dependencyInstallationDetected && ( - msg.toLowerCase().includes("failed to resolve dependencies") || - msg.toLowerCase().includes("installation failed") || - msg.includes("× No solution found when resolving dependencies") - )) { - log.error('[BACKEND STARTUP] UV dependency installation failed'); - - // Clean up installing lock file - InstallLogs.cleanLockPath(); - log.info('[BACKEND STARTUP] Cleaned up uv_installing.lock file after failure'); - - safeMainWindowSend('install-dependencies-complete', { - success: false, - error: 'Dependency installation failed during backend startup' - }); - } } \ No newline at end of file diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index dc02fab00..0b29ebd66 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -45,6 +45,7 @@ const ToolSelect = forwardRef< // state management - remove internal selected state, use parent passed initialSelectedTools const [keyword, setKeyword] = useState(""); const [mcpList, setMcpList] = useState([]); + const [allMcpList, setAllMcpList] = useState([]); const [customMcpList, setCustomMcpList] = useState([]); const [isOpen, setIsOpen] = useState(false); const [installed, setInstalled] = useState<{ [id: number]: boolean }>({}); @@ -216,7 +217,7 @@ const ToolSelect = forwardRef< page: 1, size: 100, }).then((res) => { - setMcpList(res.items); + setAllMcpList(res.items); }); }; @@ -238,6 +239,14 @@ const ToolSelect = forwardRef< }); }; + // only surface installed MCPs from the market list + useEffect(() => { + if (!installedIds.length) { + const filtered = allMcpList.filter((item) => installedIds.includes(item.id)); + setMcpList(filtered); + } + }, [allMcpList, installedIds]); + // public save env/config logic const saveEnvAndConfig = async ( provider: string,