From 00bd879443c9de51c6ee5e227d4838905506382a Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Fri, 21 Mar 2025 11:50:49 -0400 Subject: [PATCH 1/4] feat(browser): add system browser detection for Playwright This change allows mycoder to detect and use system-installed browsers instead of requiring Playwright's bundled browsers to be installed. It improves the experience when mycoder is installed globally via npm. - Add BrowserDetector module for cross-platform browser detection - Update SessionManager to use detected system browsers - Add configuration options for browser preferences - Update documentation in README.md - Maintain compatibility with headless mode and clean sessions Fixes #333 --- README.md | 49 ++ implementation-proposal.md | 470 ++++++++++++++++++ mycoder.config.js | 12 + packages/agent/CHANGELOG.md | 28 +- .../src/tools/session/lib/BrowserDetector.ts | 257 ++++++++++ .../src/tools/session/lib/SessionManager.ts | 144 +++++- packages/agent/src/tools/session/lib/types.ts | 6 + .../agent/src/tools/session/sessionStart.ts | 67 ++- packages/cli/CHANGELOG.md | 12 +- 9 files changed, 995 insertions(+), 50 deletions(-) create mode 100644 implementation-proposal.md create mode 100644 packages/agent/src/tools/session/lib/BrowserDetector.ts diff --git a/README.md b/README.md index 72dfe57..67c178b 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,18 @@ export default { userSession: false, pageFilter: 'none', // 'simple', 'none', or 'readability' + // System browser detection settings + browser: { + // Whether to use system browsers or Playwright's bundled browsers + useSystemBrowsers: true, + + // Preferred browser type (chromium, firefox, webkit) + preferredType: 'chromium', + + // Custom browser executable path (overrides automatic detection) + // executablePath: null, // e.g., '/path/to/chrome' + }, + // Model settings provider: 'anthropic', model: 'claude-3-7-sonnet-20250219', @@ -209,6 +221,43 @@ MyCoder follows the [Conventional Commits](https://www.conventionalcommits.org/) For more details, see the [Contributing Guide](CONTRIBUTING.md). +## Browser Automation + +MyCoder uses Playwright for browser automation, which is used by the `sessionStart` and `sessionMessage` tools. By default, Playwright requires browsers to be installed separately via `npx playwright install`. + +### System Browser Detection + +MyCoder now includes a system browser detection feature that allows it to use your existing installed browsers instead of requiring separate Playwright browser installations. This is particularly useful when MyCoder is installed globally. + +The system browser detection: + +1. Automatically detects installed browsers on Windows, macOS, and Linux +2. Supports Chrome, Edge, Firefox, and other browsers +3. Maintains headless mode and clean session capabilities +4. Falls back to Playwright's bundled browsers if no system browser is found + +### Configuration + +You can configure the browser detection in your `mycoder.config.js`: + +```js +export default { + // Other configuration... + + // System browser detection settings + browser: { + // Whether to use system browsers or Playwright's bundled browsers + useSystemBrowsers: true, + + // Preferred browser type (chromium, firefox, webkit) + preferredType: 'chromium', + + // Custom browser executable path (overrides automatic detection) + // executablePath: null, // e.g., '/path/to/chrome' + }, +}; +``` + ## Contributing Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project. diff --git a/implementation-proposal.md b/implementation-proposal.md new file mode 100644 index 0000000..bf7c007 --- /dev/null +++ b/implementation-proposal.md @@ -0,0 +1,470 @@ +# Mycoder System Browser Detection Implementation Proposal + +## Problem Statement + +When mycoder is installed globally via `npm install -g mycoder`, users encounter issues with the browser automation functionality. This is because Playwright (the library used for browser automation) requires browsers to be installed separately, and these browsers are not automatically installed with the global npm installation. + +## Proposed Solution + +Modify mycoder to detect and use system-installed browsers (Chrome, Edge, Firefox, or Safari) instead of relying on Playwright's own browser installations. The solution will: + +1. Look for existing installed browsers on the user's system in a cross-platform way (Windows, macOS, Linux) +2. Use the detected browser for automation via Playwright's `executablePath` option +3. Maintain the ability to run browsers in headless mode +4. Preserve the clean session behavior (equivalent to incognito/private browsing) + +## Implementation Details + +### 1. Create a Browser Detection Module + +Create a new module in the agent package to handle browser detection across platforms: + +```typescript +// packages/agent/src/tools/session/lib/BrowserDetector.ts + +import fs from 'fs'; +import path from 'path'; +import { homedir } from 'os'; +import { execSync } from 'child_process'; + +export interface BrowserInfo { + name: string; + type: 'chromium' | 'firefox' | 'webkit'; + path: string; +} + +export class BrowserDetector { + /** + * Detect available browsers on the system + * Returns an array of browser information objects sorted by preference + */ + static async detectBrowsers(): Promise { + const platform = process.platform; + + let browsers: BrowserInfo[] = []; + + switch (platform) { + case 'darwin': + browsers = await this.detectMacOSBrowsers(); + break; + case 'win32': + browsers = await this.detectWindowsBrowsers(); + break; + case 'linux': + browsers = await this.detectLinuxBrowsers(); + break; + default: + break; + } + + return browsers; + } + + /** + * Detect browsers on macOS + */ + private static async detectMacOSBrowsers(): Promise { + const browsers: BrowserInfo[] = []; + + // Chrome paths + const chromePaths = [ + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', + `${homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`, + `${homedir()}/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary`, + ]; + + // Edge paths + const edgePaths = [ + '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', + `${homedir()}/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge`, + ]; + + // Firefox paths + const firefoxPaths = [ + '/Applications/Firefox.app/Contents/MacOS/firefox', + '/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox', + '/Applications/Firefox Nightly.app/Contents/MacOS/firefox', + `${homedir()}/Applications/Firefox.app/Contents/MacOS/firefox`, + ]; + + // Check Chrome paths + for (const chromePath of chromePaths) { + if (this.canAccess(chromePath)) { + browsers.push({ + name: 'Chrome', + type: 'chromium', + path: chromePath, + }); + } + } + + // Check Edge paths + for (const edgePath of edgePaths) { + if (this.canAccess(edgePath)) { + browsers.push({ + name: 'Edge', + type: 'chromium', // Edge is Chromium-based + path: edgePath, + }); + } + } + + // Check Firefox paths + for (const firefoxPath of firefoxPaths) { + if (this.canAccess(firefoxPath)) { + browsers.push({ + name: 'Firefox', + type: 'firefox', + path: firefoxPath, + }); + } + } + + return browsers; + } + + /** + * Detect browsers on Windows + */ + private static async detectWindowsBrowsers(): Promise { + const browsers: BrowserInfo[] = []; + + // Common installation paths for Chrome + const chromePaths = [ + path.join( + process.env.LOCALAPPDATA || '', + 'Google/Chrome/Application/chrome.exe', + ), + path.join( + process.env.PROGRAMFILES || '', + 'Google/Chrome/Application/chrome.exe', + ), + path.join( + process.env['PROGRAMFILES(X86)'] || '', + 'Google/Chrome/Application/chrome.exe', + ), + ]; + + // Common installation paths for Edge + const edgePaths = [ + path.join( + process.env.LOCALAPPDATA || '', + 'Microsoft/Edge/Application/msedge.exe', + ), + path.join( + process.env.PROGRAMFILES || '', + 'Microsoft/Edge/Application/msedge.exe', + ), + path.join( + process.env['PROGRAMFILES(X86)'] || '', + 'Microsoft/Edge/Application/msedge.exe', + ), + ]; + + // Common installation paths for Firefox + const firefoxPaths = [ + path.join(process.env.PROGRAMFILES || '', 'Mozilla Firefox/firefox.exe'), + path.join( + process.env['PROGRAMFILES(X86)'] || '', + 'Mozilla Firefox/firefox.exe', + ), + ]; + + // Check Chrome paths + for (const chromePath of chromePaths) { + if (this.canAccess(chromePath)) { + browsers.push({ + name: 'Chrome', + type: 'chromium', + path: chromePath, + }); + } + } + + // Check Edge paths + for (const edgePath of edgePaths) { + if (this.canAccess(edgePath)) { + browsers.push({ + name: 'Edge', + type: 'chromium', // Edge is Chromium-based + path: edgePath, + }); + } + } + + // Check Firefox paths + for (const firefoxPath of firefoxPaths) { + if (this.canAccess(firefoxPath)) { + browsers.push({ + name: 'Firefox', + type: 'firefox', + path: firefoxPath, + }); + } + } + + return browsers; + } + + /** + * Detect browsers on Linux + */ + private static async detectLinuxBrowsers(): Promise { + const browsers: BrowserInfo[] = []; + + // Try to find Chrome/Chromium using the 'which' command + const chromiumExecutables = [ + 'google-chrome-stable', + 'google-chrome', + 'chromium-browser', + 'chromium', + ]; + + // Try to find Firefox using the 'which' command + const firefoxExecutables = ['firefox']; + + // Check for Chrome/Chromium + for (const executable of chromiumExecutables) { + try { + const browserPath = execSync(`which ${executable}`, { stdio: 'pipe' }) + .toString() + .trim(); + if (this.canAccess(browserPath)) { + browsers.push({ + name: executable, + type: 'chromium', + path: browserPath, + }); + } + } catch (e) { + // Not installed + } + } + + // Check for Firefox + for (const executable of firefoxExecutables) { + try { + const browserPath = execSync(`which ${executable}`, { stdio: 'pipe' }) + .toString() + .trim(); + if (this.canAccess(browserPath)) { + browsers.push({ + name: 'Firefox', + type: 'firefox', + path: browserPath, + }); + } + } catch (e) { + // Not installed + } + } + + return browsers; + } + + /** + * Check if a file exists and is accessible + */ + private static canAccess(filePath: string): boolean { + try { + fs.accessSync(filePath); + return true; + } catch (e) { + return false; + } + } +} +``` + +### 2. Modify the SessionManager to Use Detected Browsers + +Update the SessionManager to use the browser detection module: + +```typescript +// packages/agent/src/tools/session/lib/SessionManager.ts + +import { chromium, firefox } from '@playwright/test'; +import { v4 as uuidv4 } from 'uuid'; + +import { + BrowserConfig, + Session, + BrowserError, + BrowserErrorCode, +} from './types.js'; +import { BrowserDetector, BrowserInfo } from './BrowserDetector.js'; + +export class SessionManager { + private sessions: Map = new Map(); + private readonly defaultConfig: BrowserConfig = { + headless: true, + defaultTimeout: 30000, + }; + private detectedBrowsers: BrowserInfo[] = []; + private browserDetectionPromise: Promise | null = null; + + constructor() { + // Store a reference to the instance globally for cleanup + (globalThis as any).__BROWSER_MANAGER__ = this; + + // Set up cleanup handlers for graceful shutdown + this.setupGlobalCleanup(); + + // Start browser detection in the background + this.browserDetectionPromise = this.detectBrowsers(); + } + + /** + * Detect available browsers on the system + */ + private async detectBrowsers(): Promise { + try { + this.detectedBrowsers = await BrowserDetector.detectBrowsers(); + console.log( + `Detected ${this.detectedBrowsers.length} browsers on the system`, + ); + } catch (error) { + console.error('Failed to detect system browsers:', error); + this.detectedBrowsers = []; + } + } + + async createSession(config?: BrowserConfig): Promise { + try { + // Wait for browser detection to complete if it's still running + if (this.browserDetectionPromise) { + await this.browserDetectionPromise; + this.browserDetectionPromise = null; + } + + const sessionConfig = { ...this.defaultConfig, ...config }; + + // Try to use a system browser if any were detected + let browser; + let browserInfo: BrowserInfo | undefined; + + // Prefer Chrome/Edge (Chromium-based browsers) + browserInfo = this.detectedBrowsers.find((b) => b.type === 'chromium'); + + if (browserInfo) { + console.log( + `Using system browser: ${browserInfo.name} at ${browserInfo.path}`, + ); + + // Launch the browser using the detected executable path + if (browserInfo.type === 'chromium') { + browser = await chromium.launch({ + headless: sessionConfig.headless, + executablePath: browserInfo.path, + }); + } else if (browserInfo.type === 'firefox') { + browser = await firefox.launch({ + headless: sessionConfig.headless, + executablePath: browserInfo.path, + }); + } + } + + // Fall back to Playwright's bundled browser if no system browser was found or launch failed + if (!browser) { + console.log( + 'No system browser detected or failed to launch, trying bundled browser', + ); + browser = await chromium.launch({ + headless: sessionConfig.headless, + }); + } + + // Create a new context (equivalent to incognito) + const context = await browser.newContext({ + viewport: null, + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + }); + + const page = await context.newPage(); + page.setDefaultTimeout(sessionConfig.defaultTimeout ?? 30000); + + const session: Session = { + browser, + page, + id: uuidv4(), + }; + + this.sessions.set(session.id, session); + this.setupCleanup(session); + + return session; + } catch (error) { + throw new BrowserError( + 'Failed to create browser session', + BrowserErrorCode.LAUNCH_FAILED, + error, + ); + } + } + + // Rest of the class remains the same... +} +``` + +### 3. Add Configuration Options + +Allow users to configure browser preferences in their mycoder.config.js: + +```typescript +// Example mycoder.config.js with browser configuration +export default { + // ... existing config + + // Browser configuration + browser: { + // Specify a custom browser executable path (overrides automatic detection) + executablePath: null, // e.g., '/path/to/chrome' + + // Preferred browser type (chromium, firefox, webkit) + preferredType: 'chromium', + + // Whether to use system browsers or Playwright's bundled browsers + useSystemBrowsers: true, + + // Whether to run in headless mode + headless: true, + }, +}; +``` + +### 4. Update Documentation + +Add information to the README.md about the browser detection feature and how to configure it. + +## Benefits + +1. **Improved User Experience**: Users can install mycoder globally without needing to manually install Playwright browsers. +2. **Reduced Disk Space**: Avoids duplicate browser installations if the user already has browsers installed. +3. **Cross-Platform Compatibility**: Works on Windows, macOS, and Linux. +4. **Flexibility**: Users can still configure custom browser paths if needed. + +## Potential Challenges + +1. **Compatibility Issues**: Playwright warns about compatibility with non-bundled browsers. We should test with different browser versions. +2. **Browser Versions**: Some features might not work with older browser versions. +3. **Headless Mode Support**: Not all system browsers might support headless mode in the same way. + +## Testing Plan + +1. Test browser detection on all three major platforms (Windows, macOS, Linux) +2. Test with different browser versions +3. Test headless mode functionality +4. Test incognito/clean session behavior +5. Test with custom browser paths + +## Implementation Timeline + +1. Create the browser detection module +2. Modify the SessionManager to use detected browsers +3. Add configuration options +4. Update documentation +5. Test on different platforms +6. Release as part of the next version update diff --git a/mycoder.config.js b/mycoder.config.js index e8a6e82..638b983 100644 --- a/mycoder.config.js +++ b/mycoder.config.js @@ -8,6 +8,18 @@ export default { userSession: false, pageFilter: 'none', // 'simple', 'none', or 'readability' + // System browser detection settings + browser: { + // Whether to use system browsers or Playwright's bundled browsers + useSystemBrowsers: true, + + // Preferred browser type (chromium, firefox, webkit) + preferredType: 'chromium', + + // Custom browser executable path (overrides automatic detection) + // executablePath: null, // e.g., '/path/to/chrome' + }, + // Model settings //provider: 'anthropic', //model: 'claude-3-7-sonnet-20250219', diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index a82c4e8..572f753 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,25 +1,23 @@ # [mycoder-agent-v1.5.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.4.2...mycoder-agent-v1.5.0) (2025-03-20) - ### Bug Fixes -* improve resource trackers and fix tests ([c31546e](https://github.com/drivecore/mycoder/commit/c31546ea0375ce7fa477d7e0e4f11ea1e2b6d65e)) -* properly format agentDone tool completion message ([8d19c41](https://github.com/drivecore/mycoder/commit/8d19c410db52190cc871c201b133bee127757599)) -* resolve build and test issues ([549f0c7](https://github.com/drivecore/mycoder/commit/549f0c7184e48d2bd3221bf063f74255799da275)) -* resolve TypeError in interactive mode ([6e5e191](https://github.com/drivecore/mycoder/commit/6e5e1912d69906674f5c7fec9b79495de79b63c6)) -* restore visibility of tool execution output ([0809694](https://github.com/drivecore/mycoder/commit/0809694538d8bc7d808de4f1b9b97cd3a718941c)), closes [#328](https://github.com/drivecore/mycoder/issues/328) -* shell message should reset output on each read ([670a10b](https://github.com/drivecore/mycoder/commit/670a10bd841307750c95796d621b7d099d0e83c1)) -* update CLI cleanup to use ShellTracker instead of processStates ([3dca767](https://github.com/drivecore/mycoder/commit/3dca7670bed4884650b43d431c09a14d2673eb58)) - +- improve resource trackers and fix tests ([c31546e](https://github.com/drivecore/mycoder/commit/c31546ea0375ce7fa477d7e0e4f11ea1e2b6d65e)) +- properly format agentDone tool completion message ([8d19c41](https://github.com/drivecore/mycoder/commit/8d19c410db52190cc871c201b133bee127757599)) +- resolve build and test issues ([549f0c7](https://github.com/drivecore/mycoder/commit/549f0c7184e48d2bd3221bf063f74255799da275)) +- resolve TypeError in interactive mode ([6e5e191](https://github.com/drivecore/mycoder/commit/6e5e1912d69906674f5c7fec9b79495de79b63c6)) +- restore visibility of tool execution output ([0809694](https://github.com/drivecore/mycoder/commit/0809694538d8bc7d808de4f1b9b97cd3a718941c)), closes [#328](https://github.com/drivecore/mycoder/issues/328) +- shell message should reset output on each read ([670a10b](https://github.com/drivecore/mycoder/commit/670a10bd841307750c95796d621b7d099d0e83c1)) +- update CLI cleanup to use ShellTracker instead of processStates ([3dca767](https://github.com/drivecore/mycoder/commit/3dca7670bed4884650b43d431c09a14d2673eb58)) ### Features -* add colored console output for agent logs ([5f38b2d](https://github.com/drivecore/mycoder/commit/5f38b2dc4a7f952f3c484367ef5576172f1ae321)) -* Add interactive correction feature to CLI mode ([de2861f](https://github.com/drivecore/mycoder/commit/de2861f436d35db44653dc5a0c449f4f4068ca13)), closes [#326](https://github.com/drivecore/mycoder/issues/326) -* add parent-to-subagent communication in agentMessage tool ([3b11db1](https://github.com/drivecore/mycoder/commit/3b11db1063496d9fe1f8efc362257d9ea8287603)) -* add stdinContent parameter to shell commands ([5342a0f](https://github.com/drivecore/mycoder/commit/5342a0fa98424282c75ca50c93b380c85ea58a20)), closes [#301](https://github.com/drivecore/mycoder/issues/301) -* implement ShellTracker to decouple from backgroundTools ([65378e3](https://github.com/drivecore/mycoder/commit/65378e34b035699f61b701679742ba9a7e667215)) -* remove respawn capability, it wasn't being used anyhow. ([8e086b4](https://github.com/drivecore/mycoder/commit/8e086b46bd0836dfce39331aa8e6b0d5de81b275)) +- add colored console output for agent logs ([5f38b2d](https://github.com/drivecore/mycoder/commit/5f38b2dc4a7f952f3c484367ef5576172f1ae321)) +- Add interactive correction feature to CLI mode ([de2861f](https://github.com/drivecore/mycoder/commit/de2861f436d35db44653dc5a0c449f4f4068ca13)), closes [#326](https://github.com/drivecore/mycoder/issues/326) +- add parent-to-subagent communication in agentMessage tool ([3b11db1](https://github.com/drivecore/mycoder/commit/3b11db1063496d9fe1f8efc362257d9ea8287603)) +- add stdinContent parameter to shell commands ([5342a0f](https://github.com/drivecore/mycoder/commit/5342a0fa98424282c75ca50c93b380c85ea58a20)), closes [#301](https://github.com/drivecore/mycoder/issues/301) +- implement ShellTracker to decouple from backgroundTools ([65378e3](https://github.com/drivecore/mycoder/commit/65378e34b035699f61b701679742ba9a7e667215)) +- remove respawn capability, it wasn't being used anyhow. ([8e086b4](https://github.com/drivecore/mycoder/commit/8e086b46bd0836dfce39331aa8e6b0d5de81b275)) # [mycoder-agent-v1.4.2](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.4.1...mycoder-agent-v1.4.2) (2025-03-14) diff --git a/packages/agent/src/tools/session/lib/BrowserDetector.ts b/packages/agent/src/tools/session/lib/BrowserDetector.ts new file mode 100644 index 0000000..59f4bdd --- /dev/null +++ b/packages/agent/src/tools/session/lib/BrowserDetector.ts @@ -0,0 +1,257 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; +import { homedir } from 'os'; +import path from 'path'; + +export interface BrowserInfo { + name: string; + type: 'chromium' | 'firefox' | 'webkit'; + path: string; +} + +/** + * Utility class to detect system-installed browsers across platforms + */ +export class BrowserDetector { + /** + * Detect available browsers on the system + * Returns an array of browser information objects sorted by preference + */ + static async detectBrowsers(): Promise { + const platform = process.platform; + + let browsers: BrowserInfo[] = []; + + switch (platform) { + case 'darwin': + browsers = await this.detectMacOSBrowsers(); + break; + case 'win32': + browsers = await this.detectWindowsBrowsers(); + break; + case 'linux': + browsers = await this.detectLinuxBrowsers(); + break; + default: + console.log(`Unsupported platform: ${platform}`); + break; + } + + return browsers; + } + + /** + * Detect browsers on macOS + */ + private static async detectMacOSBrowsers(): Promise { + const browsers: BrowserInfo[] = []; + + // Chrome paths + const chromePaths = [ + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', + `${homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`, + `${homedir()}/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary`, + ]; + + // Edge paths + const edgePaths = [ + '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', + `${homedir()}/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge`, + ]; + + // Firefox paths + const firefoxPaths = [ + '/Applications/Firefox.app/Contents/MacOS/firefox', + '/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox', + '/Applications/Firefox Nightly.app/Contents/MacOS/firefox', + `${homedir()}/Applications/Firefox.app/Contents/MacOS/firefox`, + ]; + + // Check Chrome paths + for (const chromePath of chromePaths) { + if (this.canAccess(chromePath)) { + browsers.push({ + name: 'Chrome', + type: 'chromium', + path: chromePath, + }); + } + } + + // Check Edge paths + for (const edgePath of edgePaths) { + if (this.canAccess(edgePath)) { + browsers.push({ + name: 'Edge', + type: 'chromium', // Edge is Chromium-based + path: edgePath, + }); + } + } + + // Check Firefox paths + for (const firefoxPath of firefoxPaths) { + if (this.canAccess(firefoxPath)) { + browsers.push({ + name: 'Firefox', + type: 'firefox', + path: firefoxPath, + }); + } + } + + return browsers; + } + + /** + * Detect browsers on Windows + */ + private static async detectWindowsBrowsers(): Promise { + const browsers: BrowserInfo[] = []; + + // Common installation paths for Chrome + const chromePaths = [ + path.join( + process.env.LOCALAPPDATA || '', + 'Google/Chrome/Application/chrome.exe', + ), + path.join( + process.env.PROGRAMFILES || '', + 'Google/Chrome/Application/chrome.exe', + ), + path.join( + process.env['PROGRAMFILES(X86)'] || '', + 'Google/Chrome/Application/chrome.exe', + ), + ]; + + // Common installation paths for Edge + const edgePaths = [ + path.join( + process.env.LOCALAPPDATA || '', + 'Microsoft/Edge/Application/msedge.exe', + ), + path.join( + process.env.PROGRAMFILES || '', + 'Microsoft/Edge/Application/msedge.exe', + ), + path.join( + process.env['PROGRAMFILES(X86)'] || '', + 'Microsoft/Edge/Application/msedge.exe', + ), + ]; + + // Common installation paths for Firefox + const firefoxPaths = [ + path.join(process.env.PROGRAMFILES || '', 'Mozilla Firefox/firefox.exe'), + path.join( + process.env['PROGRAMFILES(X86)'] || '', + 'Mozilla Firefox/firefox.exe', + ), + ]; + + // Check Chrome paths + for (const chromePath of chromePaths) { + if (this.canAccess(chromePath)) { + browsers.push({ + name: 'Chrome', + type: 'chromium', + path: chromePath, + }); + } + } + + // Check Edge paths + for (const edgePath of edgePaths) { + if (this.canAccess(edgePath)) { + browsers.push({ + name: 'Edge', + type: 'chromium', // Edge is Chromium-based + path: edgePath, + }); + } + } + + // Check Firefox paths + for (const firefoxPath of firefoxPaths) { + if (this.canAccess(firefoxPath)) { + browsers.push({ + name: 'Firefox', + type: 'firefox', + path: firefoxPath, + }); + } + } + + return browsers; + } + + /** + * Detect browsers on Linux + */ + private static async detectLinuxBrowsers(): Promise { + const browsers: BrowserInfo[] = []; + + // Try to find Chrome/Chromium using the 'which' command + const chromiumExecutables = [ + 'google-chrome-stable', + 'google-chrome', + 'chromium-browser', + 'chromium', + ]; + + // Try to find Firefox using the 'which' command + const firefoxExecutables = ['firefox']; + + // Check for Chrome/Chromium + for (const executable of chromiumExecutables) { + try { + const browserPath = execSync(`which ${executable}`, { stdio: 'pipe' }) + .toString() + .trim(); + if (this.canAccess(browserPath)) { + browsers.push({ + name: executable, + type: 'chromium', + path: browserPath, + }); + } + } catch { + // Not installed + } + } + + // Check for Firefox + for (const executable of firefoxExecutables) { + try { + const browserPath = execSync(`which ${executable}`, { stdio: 'pipe' }) + .toString() + .trim(); + if (this.canAccess(browserPath)) { + browsers.push({ + name: 'Firefox', + type: 'firefox', + path: browserPath, + }); + } + } catch { + // Not installed + } + } + + return browsers; + } + + /** + * Check if a file exists and is accessible + */ + private static canAccess(filePath: string): boolean { + try { + fs.accessSync(filePath); + return true; + } catch { + return false; + } + } +} diff --git a/packages/agent/src/tools/session/lib/SessionManager.ts b/packages/agent/src/tools/session/lib/SessionManager.ts index cd747ed..4500c2b 100644 --- a/packages/agent/src/tools/session/lib/SessionManager.ts +++ b/packages/agent/src/tools/session/lib/SessionManager.ts @@ -1,6 +1,7 @@ -import { chromium } from '@playwright/test'; +import { chromium, firefox, webkit } from '@playwright/test'; import { v4 as uuidv4 } from 'uuid'; +import { BrowserDetector, BrowserInfo } from './BrowserDetector.js'; import { BrowserConfig, Session, @@ -13,7 +14,11 @@ export class SessionManager { private readonly defaultConfig: BrowserConfig = { headless: true, defaultTimeout: 30000, + useSystemBrowsers: true, + preferredType: 'chromium', }; + private detectedBrowsers: BrowserInfo[] = []; + private browserDetectionPromise: Promise | null = null; constructor() { // Store a reference to the instance globally for cleanup @@ -22,16 +27,90 @@ export class SessionManager { // Set up cleanup handlers for graceful shutdown this.setupGlobalCleanup(); + + // Start browser detection in the background + this.browserDetectionPromise = this.detectBrowsers(); + } + + /** + * Detect available browsers on the system + */ + private async detectBrowsers(): Promise { + try { + this.detectedBrowsers = await BrowserDetector.detectBrowsers(); + console.log( + `Detected ${this.detectedBrowsers.length} browsers on the system`, + ); + if (this.detectedBrowsers.length > 0) { + console.log('Available browsers:'); + this.detectedBrowsers.forEach((browser) => { + console.log(`- ${browser.name} (${browser.type}) at ${browser.path}`); + }); + } + } catch (error) { + console.error('Failed to detect system browsers:', error); + this.detectedBrowsers = []; + } } async createSession(config?: BrowserConfig): Promise { try { + // Wait for browser detection to complete if it's still running + if (this.browserDetectionPromise) { + await this.browserDetectionPromise; + this.browserDetectionPromise = null; + } + const sessionConfig = { ...this.defaultConfig, ...config }; + + // Determine if we should try to use system browsers + const useSystemBrowsers = sessionConfig.useSystemBrowsers !== false; + + // If a specific executable path is provided, use that + if (sessionConfig.executablePath) { + console.log( + `Using specified browser executable: ${sessionConfig.executablePath}`, + ); + return this.launchWithExecutablePath( + sessionConfig.executablePath, + sessionConfig.preferredType || 'chromium', + sessionConfig, + ); + } + + // Try to use a system browser if enabled and any were detected + if (useSystemBrowsers && this.detectedBrowsers.length > 0) { + const preferredType = sessionConfig.preferredType || 'chromium'; + + // First try to find a browser of the preferred type + let browserInfo = this.detectedBrowsers.find( + (b) => b.type === preferredType, + ); + + // If no preferred browser type found, use any available browser + if (!browserInfo) { + browserInfo = this.detectedBrowsers[0]; + } + + if (browserInfo) { + console.log( + `Using system browser: ${browserInfo.name} (${browserInfo.type}) at ${browserInfo.path}`, + ); + return this.launchWithExecutablePath( + browserInfo.path, + browserInfo.type, + sessionConfig, + ); + } + } + + // Fall back to Playwright's bundled browser + console.log('Using Playwright bundled browser'); const browser = await chromium.launch({ headless: sessionConfig.headless, }); - // Create a new context (equivalent to Puppeteer's incognito context) + // Create a new context (equivalent to incognito) const context = await browser.newContext({ viewport: null, userAgent: @@ -39,7 +118,7 @@ export class SessionManager { }); const page = await context.newPage(); - page.setDefaultTimeout(sessionConfig.defaultTimeout ?? 1000); + page.setDefaultTimeout(sessionConfig.defaultTimeout ?? 30000); const session: Session = { browser, @@ -60,6 +139,65 @@ export class SessionManager { } } + /** + * Launch a browser with a specific executable path + */ + private async launchWithExecutablePath( + executablePath: string, + browserType: 'chromium' | 'firefox' | 'webkit', + config: BrowserConfig, + ): Promise { + let browser; + + // Launch the browser using the detected executable path + switch (browserType) { + case 'chromium': + browser = await chromium.launch({ + headless: config.headless, + executablePath: executablePath, + }); + break; + case 'firefox': + browser = await firefox.launch({ + headless: config.headless, + executablePath: executablePath, + }); + break; + case 'webkit': + browser = await webkit.launch({ + headless: config.headless, + executablePath: executablePath, + }); + break; + default: + throw new BrowserError( + `Unsupported browser type: ${browserType}`, + BrowserErrorCode.LAUNCH_FAILED, + ); + } + + // Create a new context (equivalent to incognito) + const context = await browser.newContext({ + viewport: null, + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + }); + + const page = await context.newPage(); + page.setDefaultTimeout(config.defaultTimeout ?? 30000); + + const session: Session = { + browser, + page, + id: uuidv4(), + }; + + this.sessions.set(session.id, session); + this.setupCleanup(session); + + return session; + } + async closeSession(sessionId: string): Promise { const session = this.sessions.get(sessionId); if (!session) { diff --git a/packages/agent/src/tools/session/lib/types.ts b/packages/agent/src/tools/session/lib/types.ts index 4e208e8..ae19052 100644 --- a/packages/agent/src/tools/session/lib/types.ts +++ b/packages/agent/src/tools/session/lib/types.ts @@ -4,6 +4,12 @@ import type { Browser, Page } from '@playwright/test'; export interface BrowserConfig { headless?: boolean; defaultTimeout?: number; + // Custom browser executable path (overrides automatic detection) + executablePath?: string; + // Preferred browser type (chromium, firefox, webkit) + preferredType?: 'chromium' | 'firefox' | 'webkit'; + // Whether to use system browsers or Playwright's bundled browsers + useSystemBrowsers?: boolean; } // Browser session diff --git a/packages/agent/src/tools/session/sessionStart.ts b/packages/agent/src/tools/session/sessionStart.ts index 9ab6760..fc1cd81 100644 --- a/packages/agent/src/tools/session/sessionStart.ts +++ b/packages/agent/src/tools/session/sessionStart.ts @@ -1,4 +1,3 @@ -import { chromium } from '@playwright/test'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; @@ -6,8 +5,10 @@ import { Tool } from '../../core/types.js'; import { errorToString } from '../../utils/errorToString.js'; import { sleep } from '../../utils/sleep.js'; +import { BrowserDetector } from './lib/BrowserDetector.js'; import { filterPageContent } from './lib/filterPageContent.js'; -import { browserSessions } from './lib/types.js'; +import { SessionManager } from './lib/SessionManager.js'; +import { browserSessions, BrowserConfig } from './lib/types.js'; import { SessionStatus } from './SessionTracker.js'; const parameterSchema = z.object({ @@ -48,9 +49,11 @@ export const sessionStartTool: Tool = { userSession, pageFilter, browserTracker, - ..._ // Unused parameters + ...context // Other parameters }, ): Promise => { + // Get config from context if available + const config = (context as any).config || {}; logger.debug(`Starting browser session${url ? ` at ${url}` : ''}`); logger.debug(`User session mode: ${userSession ? 'enabled' : 'disabled'}`); logger.debug(`Webpage processing mode: ${pageFilter}`); @@ -59,40 +62,54 @@ export const sessionStartTool: Tool = { // Register this browser session with the tracker const instanceId = browserTracker.registerBrowser(url); - // Launch browser - const launchOptions = { + // Get browser configuration from config + const browserConfig = config.browser || {}; + + // Create browser configuration + const sessionConfig: BrowserConfig = { headless, + defaultTimeout: timeout, + useSystemBrowsers: browserConfig.useSystemBrowsers !== false, + preferredType: browserConfig.preferredType || 'chromium', + executablePath: browserConfig.executablePath, }; - // Use system Chrome installation if userSession is true + // If userSession is true, use system Chrome if (userSession) { - logger.debug('Using system Chrome installation'); - // For Chrome, we use the channel option to specify Chrome - launchOptions['channel'] = 'chrome'; + logger.debug('User session mode enabled, forcing system Chrome'); + sessionConfig.useSystemBrowsers = true; + sessionConfig.preferredType = 'chromium'; + + // Try to detect Chrome browser + const browsers = await BrowserDetector.detectBrowsers(); + const chrome = browsers.find((b) => + b.name.toLowerCase().includes('chrome'), + ); + if (chrome) { + logger.debug(`Found system Chrome at ${chrome.path}`); + sessionConfig.executablePath = chrome.path; + } } - const browser = await chromium.launch(launchOptions); + logger.debug(`Browser config: ${JSON.stringify(sessionConfig)}`); - // Create new context with default settings - const context = await browser.newContext({ - viewport: null, - userAgent: - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - serviceWorkers: 'block', // Block service workers which can cause continuous network activity - }); + // Create a session manager and launch browser + const sessionManager = new SessionManager(); + const session = await sessionManager.createSession(sessionConfig); - // Create new page - const page = await context.newPage(); - page.setDefaultTimeout(timeout); + // Set the default timeout + session.page.setDefaultTimeout(timeout); - // Initialize browser session - const session = { + // Get references to the browser and page + const browser = session.browser; + const page = session.page; + + // Store the session in the browserSessions map for compatibility + browserSessions.set(instanceId, { browser, page, id: instanceId, - }; - - browserSessions.set(instanceId, session); + }); // Setup cleanup handlers browser.on('disconnected', () => { diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 2e65f69..fb55382 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,17 +1,15 @@ # [mycoder-v1.5.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.4.1...mycoder-v1.5.0) (2025-03-20) - ### Bug Fixes -* list default model correctly in logging ([5b67b58](https://github.com/drivecore/mycoder/commit/5b67b581cb6a7259bf1718098ed57ad2bf96f947)) -* restore visibility of tool execution output ([0809694](https://github.com/drivecore/mycoder/commit/0809694538d8bc7d808de4f1b9b97cd3a718941c)), closes [#328](https://github.com/drivecore/mycoder/issues/328) -* update CLI cleanup to use ShellTracker instead of processStates ([3dca767](https://github.com/drivecore/mycoder/commit/3dca7670bed4884650b43d431c09a14d2673eb58)) - +- list default model correctly in logging ([5b67b58](https://github.com/drivecore/mycoder/commit/5b67b581cb6a7259bf1718098ed57ad2bf96f947)) +- restore visibility of tool execution output ([0809694](https://github.com/drivecore/mycoder/commit/0809694538d8bc7d808de4f1b9b97cd3a718941c)), closes [#328](https://github.com/drivecore/mycoder/issues/328) +- update CLI cleanup to use ShellTracker instead of processStates ([3dca767](https://github.com/drivecore/mycoder/commit/3dca7670bed4884650b43d431c09a14d2673eb58)) ### Features -* Add interactive correction feature to CLI mode ([de2861f](https://github.com/drivecore/mycoder/commit/de2861f436d35db44653dc5a0c449f4f4068ca13)), closes [#326](https://github.com/drivecore/mycoder/issues/326) -* add stdinContent parameter to shell commands ([5342a0f](https://github.com/drivecore/mycoder/commit/5342a0fa98424282c75ca50c93b380c85ea58a20)), closes [#301](https://github.com/drivecore/mycoder/issues/301) +- Add interactive correction feature to CLI mode ([de2861f](https://github.com/drivecore/mycoder/commit/de2861f436d35db44653dc5a0c449f4f4068ca13)), closes [#326](https://github.com/drivecore/mycoder/issues/326) +- add stdinContent parameter to shell commands ([5342a0f](https://github.com/drivecore/mycoder/commit/5342a0fa98424282c75ca50c93b380c85ea58a20)), closes [#301](https://github.com/drivecore/mycoder/issues/301) # [mycoder-v1.4.1](https://github.com/drivecore/mycoder/compare/mycoder-v1.4.0...mycoder-v1.4.1) (2025-03-14) From 4fd8b482fd415ff9d5f71ef3b2246388412942b0 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Fri, 21 Mar 2025 12:48:24 -0400 Subject: [PATCH 2/4] chore: remove some old docs. --- implementation-proposal.md | 470 ------------------------------------- 1 file changed, 470 deletions(-) delete mode 100644 implementation-proposal.md diff --git a/implementation-proposal.md b/implementation-proposal.md deleted file mode 100644 index bf7c007..0000000 --- a/implementation-proposal.md +++ /dev/null @@ -1,470 +0,0 @@ -# Mycoder System Browser Detection Implementation Proposal - -## Problem Statement - -When mycoder is installed globally via `npm install -g mycoder`, users encounter issues with the browser automation functionality. This is because Playwright (the library used for browser automation) requires browsers to be installed separately, and these browsers are not automatically installed with the global npm installation. - -## Proposed Solution - -Modify mycoder to detect and use system-installed browsers (Chrome, Edge, Firefox, or Safari) instead of relying on Playwright's own browser installations. The solution will: - -1. Look for existing installed browsers on the user's system in a cross-platform way (Windows, macOS, Linux) -2. Use the detected browser for automation via Playwright's `executablePath` option -3. Maintain the ability to run browsers in headless mode -4. Preserve the clean session behavior (equivalent to incognito/private browsing) - -## Implementation Details - -### 1. Create a Browser Detection Module - -Create a new module in the agent package to handle browser detection across platforms: - -```typescript -// packages/agent/src/tools/session/lib/BrowserDetector.ts - -import fs from 'fs'; -import path from 'path'; -import { homedir } from 'os'; -import { execSync } from 'child_process'; - -export interface BrowserInfo { - name: string; - type: 'chromium' | 'firefox' | 'webkit'; - path: string; -} - -export class BrowserDetector { - /** - * Detect available browsers on the system - * Returns an array of browser information objects sorted by preference - */ - static async detectBrowsers(): Promise { - const platform = process.platform; - - let browsers: BrowserInfo[] = []; - - switch (platform) { - case 'darwin': - browsers = await this.detectMacOSBrowsers(); - break; - case 'win32': - browsers = await this.detectWindowsBrowsers(); - break; - case 'linux': - browsers = await this.detectLinuxBrowsers(); - break; - default: - break; - } - - return browsers; - } - - /** - * Detect browsers on macOS - */ - private static async detectMacOSBrowsers(): Promise { - const browsers: BrowserInfo[] = []; - - // Chrome paths - const chromePaths = [ - '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', - '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', - `${homedir()}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`, - `${homedir()}/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary`, - ]; - - // Edge paths - const edgePaths = [ - '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', - `${homedir()}/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge`, - ]; - - // Firefox paths - const firefoxPaths = [ - '/Applications/Firefox.app/Contents/MacOS/firefox', - '/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox', - '/Applications/Firefox Nightly.app/Contents/MacOS/firefox', - `${homedir()}/Applications/Firefox.app/Contents/MacOS/firefox`, - ]; - - // Check Chrome paths - for (const chromePath of chromePaths) { - if (this.canAccess(chromePath)) { - browsers.push({ - name: 'Chrome', - type: 'chromium', - path: chromePath, - }); - } - } - - // Check Edge paths - for (const edgePath of edgePaths) { - if (this.canAccess(edgePath)) { - browsers.push({ - name: 'Edge', - type: 'chromium', // Edge is Chromium-based - path: edgePath, - }); - } - } - - // Check Firefox paths - for (const firefoxPath of firefoxPaths) { - if (this.canAccess(firefoxPath)) { - browsers.push({ - name: 'Firefox', - type: 'firefox', - path: firefoxPath, - }); - } - } - - return browsers; - } - - /** - * Detect browsers on Windows - */ - private static async detectWindowsBrowsers(): Promise { - const browsers: BrowserInfo[] = []; - - // Common installation paths for Chrome - const chromePaths = [ - path.join( - process.env.LOCALAPPDATA || '', - 'Google/Chrome/Application/chrome.exe', - ), - path.join( - process.env.PROGRAMFILES || '', - 'Google/Chrome/Application/chrome.exe', - ), - path.join( - process.env['PROGRAMFILES(X86)'] || '', - 'Google/Chrome/Application/chrome.exe', - ), - ]; - - // Common installation paths for Edge - const edgePaths = [ - path.join( - process.env.LOCALAPPDATA || '', - 'Microsoft/Edge/Application/msedge.exe', - ), - path.join( - process.env.PROGRAMFILES || '', - 'Microsoft/Edge/Application/msedge.exe', - ), - path.join( - process.env['PROGRAMFILES(X86)'] || '', - 'Microsoft/Edge/Application/msedge.exe', - ), - ]; - - // Common installation paths for Firefox - const firefoxPaths = [ - path.join(process.env.PROGRAMFILES || '', 'Mozilla Firefox/firefox.exe'), - path.join( - process.env['PROGRAMFILES(X86)'] || '', - 'Mozilla Firefox/firefox.exe', - ), - ]; - - // Check Chrome paths - for (const chromePath of chromePaths) { - if (this.canAccess(chromePath)) { - browsers.push({ - name: 'Chrome', - type: 'chromium', - path: chromePath, - }); - } - } - - // Check Edge paths - for (const edgePath of edgePaths) { - if (this.canAccess(edgePath)) { - browsers.push({ - name: 'Edge', - type: 'chromium', // Edge is Chromium-based - path: edgePath, - }); - } - } - - // Check Firefox paths - for (const firefoxPath of firefoxPaths) { - if (this.canAccess(firefoxPath)) { - browsers.push({ - name: 'Firefox', - type: 'firefox', - path: firefoxPath, - }); - } - } - - return browsers; - } - - /** - * Detect browsers on Linux - */ - private static async detectLinuxBrowsers(): Promise { - const browsers: BrowserInfo[] = []; - - // Try to find Chrome/Chromium using the 'which' command - const chromiumExecutables = [ - 'google-chrome-stable', - 'google-chrome', - 'chromium-browser', - 'chromium', - ]; - - // Try to find Firefox using the 'which' command - const firefoxExecutables = ['firefox']; - - // Check for Chrome/Chromium - for (const executable of chromiumExecutables) { - try { - const browserPath = execSync(`which ${executable}`, { stdio: 'pipe' }) - .toString() - .trim(); - if (this.canAccess(browserPath)) { - browsers.push({ - name: executable, - type: 'chromium', - path: browserPath, - }); - } - } catch (e) { - // Not installed - } - } - - // Check for Firefox - for (const executable of firefoxExecutables) { - try { - const browserPath = execSync(`which ${executable}`, { stdio: 'pipe' }) - .toString() - .trim(); - if (this.canAccess(browserPath)) { - browsers.push({ - name: 'Firefox', - type: 'firefox', - path: browserPath, - }); - } - } catch (e) { - // Not installed - } - } - - return browsers; - } - - /** - * Check if a file exists and is accessible - */ - private static canAccess(filePath: string): boolean { - try { - fs.accessSync(filePath); - return true; - } catch (e) { - return false; - } - } -} -``` - -### 2. Modify the SessionManager to Use Detected Browsers - -Update the SessionManager to use the browser detection module: - -```typescript -// packages/agent/src/tools/session/lib/SessionManager.ts - -import { chromium, firefox } from '@playwright/test'; -import { v4 as uuidv4 } from 'uuid'; - -import { - BrowserConfig, - Session, - BrowserError, - BrowserErrorCode, -} from './types.js'; -import { BrowserDetector, BrowserInfo } from './BrowserDetector.js'; - -export class SessionManager { - private sessions: Map = new Map(); - private readonly defaultConfig: BrowserConfig = { - headless: true, - defaultTimeout: 30000, - }; - private detectedBrowsers: BrowserInfo[] = []; - private browserDetectionPromise: Promise | null = null; - - constructor() { - // Store a reference to the instance globally for cleanup - (globalThis as any).__BROWSER_MANAGER__ = this; - - // Set up cleanup handlers for graceful shutdown - this.setupGlobalCleanup(); - - // Start browser detection in the background - this.browserDetectionPromise = this.detectBrowsers(); - } - - /** - * Detect available browsers on the system - */ - private async detectBrowsers(): Promise { - try { - this.detectedBrowsers = await BrowserDetector.detectBrowsers(); - console.log( - `Detected ${this.detectedBrowsers.length} browsers on the system`, - ); - } catch (error) { - console.error('Failed to detect system browsers:', error); - this.detectedBrowsers = []; - } - } - - async createSession(config?: BrowserConfig): Promise { - try { - // Wait for browser detection to complete if it's still running - if (this.browserDetectionPromise) { - await this.browserDetectionPromise; - this.browserDetectionPromise = null; - } - - const sessionConfig = { ...this.defaultConfig, ...config }; - - // Try to use a system browser if any were detected - let browser; - let browserInfo: BrowserInfo | undefined; - - // Prefer Chrome/Edge (Chromium-based browsers) - browserInfo = this.detectedBrowsers.find((b) => b.type === 'chromium'); - - if (browserInfo) { - console.log( - `Using system browser: ${browserInfo.name} at ${browserInfo.path}`, - ); - - // Launch the browser using the detected executable path - if (browserInfo.type === 'chromium') { - browser = await chromium.launch({ - headless: sessionConfig.headless, - executablePath: browserInfo.path, - }); - } else if (browserInfo.type === 'firefox') { - browser = await firefox.launch({ - headless: sessionConfig.headless, - executablePath: browserInfo.path, - }); - } - } - - // Fall back to Playwright's bundled browser if no system browser was found or launch failed - if (!browser) { - console.log( - 'No system browser detected or failed to launch, trying bundled browser', - ); - browser = await chromium.launch({ - headless: sessionConfig.headless, - }); - } - - // Create a new context (equivalent to incognito) - const context = await browser.newContext({ - viewport: null, - userAgent: - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - }); - - const page = await context.newPage(); - page.setDefaultTimeout(sessionConfig.defaultTimeout ?? 30000); - - const session: Session = { - browser, - page, - id: uuidv4(), - }; - - this.sessions.set(session.id, session); - this.setupCleanup(session); - - return session; - } catch (error) { - throw new BrowserError( - 'Failed to create browser session', - BrowserErrorCode.LAUNCH_FAILED, - error, - ); - } - } - - // Rest of the class remains the same... -} -``` - -### 3. Add Configuration Options - -Allow users to configure browser preferences in their mycoder.config.js: - -```typescript -// Example mycoder.config.js with browser configuration -export default { - // ... existing config - - // Browser configuration - browser: { - // Specify a custom browser executable path (overrides automatic detection) - executablePath: null, // e.g., '/path/to/chrome' - - // Preferred browser type (chromium, firefox, webkit) - preferredType: 'chromium', - - // Whether to use system browsers or Playwright's bundled browsers - useSystemBrowsers: true, - - // Whether to run in headless mode - headless: true, - }, -}; -``` - -### 4. Update Documentation - -Add information to the README.md about the browser detection feature and how to configure it. - -## Benefits - -1. **Improved User Experience**: Users can install mycoder globally without needing to manually install Playwright browsers. -2. **Reduced Disk Space**: Avoids duplicate browser installations if the user already has browsers installed. -3. **Cross-Platform Compatibility**: Works on Windows, macOS, and Linux. -4. **Flexibility**: Users can still configure custom browser paths if needed. - -## Potential Challenges - -1. **Compatibility Issues**: Playwright warns about compatibility with non-bundled browsers. We should test with different browser versions. -2. **Browser Versions**: Some features might not work with older browser versions. -3. **Headless Mode Support**: Not all system browsers might support headless mode in the same way. - -## Testing Plan - -1. Test browser detection on all three major platforms (Windows, macOS, Linux) -2. Test with different browser versions -3. Test headless mode functionality -4. Test incognito/clean session behavior -5. Test with custom browser paths - -## Implementation Timeline - -1. Create the browser detection module -2. Modify the SessionManager to use detected browsers -3. Add configuration options -4. Update documentation -5. Test on different platforms -6. Release as part of the next version update From 944c979bc0c47c087bc3bca8ea80bfa2e0e397f5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 21 Mar 2025 18:15:25 +0000 Subject: [PATCH 3/4] chore(release): 1.6.0 [skip ci] # [mycoder-agent-v1.6.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.5.0...mycoder-agent-v1.6.0) (2025-03-21) ### Features * **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) --- packages/agent/CHANGELOG.md | 7 +++++++ packages/agent/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 572f753..47f75e1 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,3 +1,10 @@ +# [mycoder-agent-v1.6.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.5.0...mycoder-agent-v1.6.0) (2025-03-21) + + +### Features + +* **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) + # [mycoder-agent-v1.5.0](https://github.com/drivecore/mycoder/compare/mycoder-agent-v1.4.2...mycoder-agent-v1.5.0) (2025-03-20) ### Bug Fixes diff --git a/packages/agent/package.json b/packages/agent/package.json index f9c46d5..7af27a4 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "mycoder-agent", - "version": "1.5.0", + "version": "1.6.0", "description": "Agent module for mycoder - an AI-powered software development assistant", "type": "module", "main": "dist/index.js", From 3a8423a8e8aca1f4d3a4e1c20d42a1ca8633d3bf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 21 Mar 2025 18:16:15 +0000 Subject: [PATCH 4/4] chore(release): 1.6.0 [skip ci] # [mycoder-v1.6.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.5.0...mycoder-v1.6.0) (2025-03-21) ### Features * **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) --- packages/cli/CHANGELOG.md | 7 +++++++ packages/cli/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index fb55382..3488d63 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,3 +1,10 @@ +# [mycoder-v1.6.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.5.0...mycoder-v1.6.0) (2025-03-21) + + +### Features + +* **browser:** add system browser detection for Playwright ([00bd879](https://github.com/drivecore/mycoder/commit/00bd879443c9de51c6ee5e227d4838905506382a)), closes [#333](https://github.com/drivecore/mycoder/issues/333) + # [mycoder-v1.5.0](https://github.com/drivecore/mycoder/compare/mycoder-v1.4.1...mycoder-v1.5.0) (2025-03-20) ### Bug Fixes diff --git a/packages/cli/package.json b/packages/cli/package.json index a804b1d..727aa0f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "mycoder", "description": "A command line tool using agent that can do arbitrary tasks, including coding tasks", - "version": "1.5.0", + "version": "1.6.0", "type": "module", "bin": "./bin/cli.js", "main": "./dist/index.js",