Skip to content

Conversation

@zerob13
Copy link
Collaborator

@zerob13 zerob13 commented Dec 18, 2025

Background

This PR introduces a fully embedded browser sandbox built on Electron BrowserView, designed to allow LLM agents to directly perceive and interact with a real browser environment.

The implementation was inspired by the browser tool definitions and interaction patterns in UI-TARS, but does not use MCP services. Instead, it adopts a similar conceptual model while integrating tools and environment state directly into the agent runtime.

Screenshot 2025-12-18 at 5 21 44 PM

Summary by CodeRabbit

  • New Features

    • Yo Browser: embedded browser with tabs, navigation, address bar, screenshots, downloads, tab tools and programmable browser toolset.
  • UI

    • Browser toolbar, placeholder, window-specific chrome height and browser vs chat layouts; app chrome integrates browser controls.
  • Settings

    • Clear YoBrowser sandbox data action added.
  • Localization

    • Added browser & YoBrowser translations across many locales.
  • Events

    • New browser-related UI events for tab/window lifecycle and visibility.

✏️ Tip: You can customize this high-level summary in your review settings.

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Warning

Rate limit exceeded

@zerob13 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 73ee886 and 2f12271.

📒 Files selected for processing (2)
  • src/main/presenter/browser/BrowserTab.ts (1 hunks)
  • src/main/presenter/browser/DownloadManager.ts (1 hunks)

Walkthrough

Adds a Yo Browser subsystem: shared browser types/events, CDP/session/screenshot/download managers, BrowserTab abstraction, tool creators and manager, YoBrowserPresenter integration into main presenter and LLM tooling, renderer UI/store components, window/tab presenter updates, and i18n entries.

Changes

Cohort / File(s) Summary
Events
src/main/events.ts, src/renderer/src/events.ts
Added YO_BROWSER_EVENTS export with six yo-browser IPC event constants (tab/window lifecycle and navigation).
Shared types & presenter API
src/shared/types/browser.ts, src/shared/types/index.d.ts, src/shared/types/presenters/legacy.presenters.d.ts
New browser types (BrowserTabInfo, BrowserTabStatus, ScreenshotOptions, DownloadInfo, BrowserToolDefinition, BrowserEvent, BrowserContextSnapshot), re-exported from index, and presenter interface extensions (IYoBrowserPresenter, window/tab options, tab.browserTabId, new TabPresenter methods).
Browser session & CDP
src/main/presenter/browser/yoBrowserSession.ts, src/main/presenter/browser/CDPManager.ts
New Yo Browser partition/session utilities and CDPManager for DevTools sessions, navigation, script evaluation, DOM access, and screenshot capture.
BrowserTab abstraction
src/main/presenter/browser/BrowserTab.ts
New BrowserTab class managing WebContents/CDP session, navigation, DOM/script eval, interactions, screenshots, lifecycle, and synchronization.
Screenshot & Download managers
src/main/presenter/browser/ScreenshotManager.ts, src/main/presenter/browser/DownloadManager.ts
Added ScreenshotManager (full-page support via layout metrics) and DownloadManager (nanoid ids, will-download handling, timeouts, lifecycle tracking).
Tooling core
src/main/presenter/browser/BrowserToolManager.ts, src/main/presenter/browser/BrowserContextBuilder.ts, src/main/presenter/browser/tools/types.ts
BrowserToolManager to aggregate/execute tools, BrowserContextBuilder for system prompt/tool summaries, and BrowserTool types/context interfaces.
Tool creators
src/main/presenter/browser/tools/*.ts
New tool factories (navigate, action, content, screenshot, tabs, download) exposing BrowserToolDefinition handlers with Zod schemas and handlers using BrowserToolContext.
YoBrowser presenter & integration
src/main/presenter/browser/YoBrowserPresenter.ts, src/main/presenter/index.ts
New YoBrowserPresenter orchestrating window/tab lifecycle, events, tool exposure; integrated into main Presenter initialization.
Tab & Window presenters
src/main/presenter/tabPresenter.ts, src/main/presenter/windowPresenter/index.ts
TabPresenter: per-window type (chat
LLM / tool-call integration
src/main/presenter/llmProviderPresenter/managers/agentLoopHandler.ts, .../toolCallProcessor.ts, .../providers/openAICompatibleProvider.ts, src/main/presenter/threadPresenter/utils/promptBuilder.ts
Tool-definition retrieval made context-aware; agent loop integrates Yo Browser tools and async tool calling; parseFunctionCalls signature extended; promptBuilder now includes browser context/tools in the system prompt.
Renderer UI & state
src/renderer/shell/App.vue, src/renderer/shell/components/AppBar.vue, src/renderer/shell/components/BrowserToolbar.vue, src/renderer/shell/components/BrowserPlaceholder.vue, src/renderer/src/stores/yoBrowser.ts, src/renderer/settings/components/DataSettings.vue
New BrowserToolbar and BrowserPlaceholder components, App.vue wiring for chromeHeight and window type, AppBar branches for windowType, Pinia yoBrowser store, and settings UI to clear YoBrowser sandbox data.
Localization
src/renderer/src/i18n/*/common.json, src/renderer/src/i18n/*/settings.json
Added browser UI strings and yoBrowser settings/toast translations across multiple locales.
Misc / small edits
src/main/presenter/sqlitePresenter/tables/conversations.ts, src/main/presenter/llmProviderPresenter/providers/geminiProvider.ts
Whitespace change in migration SQL; commented-out debug log in Gemini provider.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant R as Renderer (App / Toolbar)
    participant S as Renderer Store (yoBrowser)
    participant MP as Main Process (YoBrowserPresenter)
    participant TM as BrowserToolManager
    participant BT as BrowserTab
    participant CDP as CDPManager

    R->>MP: ensureWindow() / show()
    MP->>MP: create or reuse window
    MP->>BT: createTab(url)
    BT->>CDP: createSession(webContents)
    CDP-->>BT: session ready
    BT->>CDP: navigate(session, url)
    CDP-->>BT: navigation events
    BT-->>MP: emit TAB_CREATED / TAB_NAVIGATED
    MP-->>R: send YO_BROWSER_EVENTS via IPC
    R->>S: update tabs/visibility
    Note over R,S: UI updates toolbar/address bar

    R->>TM: executeTool('browser_click', args)
    TM->>MP: createContext()
    MP-->>TM: BrowserToolContext (getTab, createTab, downloadFile...)
    TM->>BT: perform action (click/eval)
    BT->>CDP: evaluateScript
    CDP-->>BT: result
    BT-->>TM: tool result
    TM-->>R: return tool result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Areas needing extra attention:

  • YoBrowserPresenter lifecycle, event emission, and edge-case cleanup
  • BrowserToolManager context creation and robust tab resolution
  • TabPresenter windowType, webPreferences/session wiring, and view bounds changes
  • CDPManager / ScreenshotManager full-page capture correctness and error handling
  • DownloadManager event handling, timeouts, and listener cleanup

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • deepinfect

Poem

🐇 I hopped through code and stitched a browsing den,

Tabs now bloom like clover, screenshots for my pen,
Click, scroll, and fetch — a carrot-shaped request,
Sandbox swept clean, the browser takes a rest,
Hop on, browse on, cheer — the rabbit says "Well done!"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature: an embedded Electron browser sandbox with auto-injected tools and environment state.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zerob13
Copy link
Collaborator Author

zerob13 commented Dec 18, 2025

@codex review

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/presenter/sqlitePresenter/tables/conversations.ts (2)

30-30: Use English for comments.

Comments and logs must be in English per coding guidelines. This applies to all comments in the file, including those in migration SQL blocks and method comments.

Please update all comments to English throughout the file, including:

  • Line 30: 解析 JSON 字段Parse JSON field
  • Lines 69, 72, 75, 81, 88, 94, 100, 106, 109, 115: Chinese comments in migrations
  • Lines 425, 431: Chinese comments in rename method

172-172: Fix SQL NULL binding: use null instead of string 'NULL'.

The ternary expression on Line 172 returns the string 'NULL' when enabledMcpTools is falsy. This will bind the literal string 'NULL' to the SQL parameter, not SQL NULL. The database will store the string 'NULL' instead of a NULL value.

🔎 Apply this diff to fix the null handling:
-      settings.enabledMcpTools ? JSON.stringify(settings.enabledMcpTools) : 'NULL',
+      settings.enabledMcpTools ? JSON.stringify(settings.enabledMcpTools) : null,
src/main/presenter/index.ts (1)

231-241: Missing cleanup for YoBrowserPresenter in destroy().

The destroy() method doesn't call yoBrowserPresenter.shutdown(). Based on the IYoBrowserPresenter interface (see src/shared/types/presenters/legacy.presenters.d.ts lines 216-217), this presenter has a shutdown() method that should be called to properly clean up browser windows and resources.

🔎 Apply this diff to add cleanup:
   destroy() {
     this.floatingButtonPresenter.destroy() // 销毁悬浮按钮
     this.tabPresenter.destroy()
+    this.yoBrowserPresenter.shutdown() // Clean up browser windows
     this.sqlitePresenter.close() // 关闭数据库连接
     this.shortcutPresenter.destroy() // 销毁快捷键监听
🟡 Minor comments (14)
src/renderer/src/i18n/fa-IR/common.json-88-96 (1)

88-96: Translate untranslated English keys in fa-IR locale file to Persian.

The browser object in the fa-IR (Persian) locale contains four keys with English values instead of Persian translations:

  • back: "Back"
  • forward: "Forward"
  • reload: "Reload"
  • addressPlaceholder: "Enter a URL"

All other locale files (zh-CN, en-US, ko-KR, ru-RU, zh-HK, fr-FR) have proper translations for these keys. The remaining keys in fa-IR (enterUrlDescription, enterUrlToStart, name) are correctly translated to Persian. Translate the four keys to maintain consistency with other locales and provide a proper localized experience for Persian-speaking users.

src/main/presenter/threadPresenter/handlers/searchHandler.ts.backup-301-303 (1)

301-303: Serializing raw image data may bloat the context.

block.image_data?.data could contain large base64-encoded images. Including this in the context string may cause token limit issues when sent to the LLM for query rewriting.

Consider using a placeholder like [image] instead of the actual data.

src/main/presenter/threadPresenter/managers/searchManager.ts.backup-240-242 (1)

240-242: Remove or guard production console.log.

Logging the full search prompt (which includes user queries and results) may leak sensitive data in production environments. Consider removing this or wrapping it with is.dev.

🔎 Suggested fix
-    // 记录最终生成的提示词
-    console.log('generateSearchPrompt', searchPrompt)
+    if (is.dev) {
+      console.log('generateSearchPrompt', searchPrompt)
+    }

Committable suggestion skipped: line range outside the PR's diff.

src/renderer/src/i18n/da-DK/common.json-89-92 (1)

89-92: Translate browser navigation keys to Danish.

Lines 89-92 contain English text ("Back", "Forward", "Reload", "Enter a URL") that should be translated to Danish for the da-DK locale.

Suggested Danish translations:

  • "Back" → "Tilbage"
  • "Forward" → "Frem"
  • "Reload" → "Genindlæs"
  • "Enter a URL" → "Indtast en URL"

As per coding guidelines, all user-facing strings must be properly localized for each language.

src/renderer/src/i18n/he-IL/common.json-89-92 (1)

89-92: Mixed language in Hebrew translation file

Lines 89-92 contain English text instead of Hebrew translations. This is inconsistent with the rest of the Hebrew locale file and will display English UI elements to Hebrew users.

🔎 Apply this diff to use Hebrew translations:
-    "back": "Back",
-    "forward": "Forward",
-    "reload": "Reload",
-    "addressPlaceholder": "Enter a URL",
+    "back": "חזור",
+    "forward": "קדימה",
+    "reload": "רענן",
+    "addressPlaceholder": "הזן כתובת URL",

Based on coding guidelines: Maintain consistent key-value structure across all language translation files.

src/main/presenter/browser/tools/tabs.ts-119-150 (1)

119-150: Add defensive checks for context methods in browser_close_tab.

The handler accesses context.getActiveTab and context.resolveTabId without verifying they exist. If these methods are undefined, the code will throw a runtime error.

🔎 Apply this diff to add defensive checks:
     handler: async (args, context) => {
       const parsed = CloseTabArgsSchema.parse(args)
-      if (!context.closeTab) {
+      if (!context.closeTab || !context.getActiveTab || !context.resolveTabId) {
         return {
-          content: [{ type: 'text', text: 'Tab closing not available' }],
+          content: [{ type: 'text', text: 'Tab operations not available' }],
           isError: true
         }
       }
src/main/presenter/browser/tools/tabs.ts-95-106 (1)

95-106: Missing capability check for context.getTab.

The handler checks for context.activateTab availability but calls context.getTab without first verifying it exists. This could cause a runtime error if getTab is undefined on the context.

🔎 Apply this diff to add the missing capability check:
       const parsed = SwitchTabArgsSchema.parse(args)
-      if (!context.activateTab) {
+      if (!context.activateTab || !context.getTab) {
         return {
-          content: [{ type: 'text', text: 'Tab activation not available' }],
+          content: [{ type: 'text', text: 'Tab operations not available' }],
           isError: true
         }
       }
src/main/presenter/browser/tools/navigate.ts-160-191 (1)

160-191: Same error message issue as browser_go_back.

The browser_go_forward handler has the same potential for displaying "Tab undefined not found" when no active tab exists.

🔎 Apply similar fix:
         if (!tab) {
           return {
             content: [
               {
                 type: 'text',
-                text: `Tab ${tabId} not found`
+                text: tabId ? `Tab ${tabId} not found` : 'No active tab available'
               }
             ],
             isError: true
           }
         }
src/main/presenter/browser/tools/navigate.ts-128-158 (1)

128-158: Error message may display "Tab undefined not found".

When resolveTabId returns an undefined value (e.g., no active tab exists), the error message on line 142 will display "Tab undefined not found", which is confusing to users.

🔎 Consider improving the error message:
         if (!tab) {
           return {
             content: [
               {
                 type: 'text',
-                text: `Tab ${tabId} not found`
+                text: tabId ? `Tab ${tabId} not found` : 'No active tab available'
               }
             ],
             isError: true
           }
         }
src/shared/types/presenters/legacy.presenters.d.ts-183-186 (1)

183-186: Duplicate type declaration: BrowserContextSnapshot is already imported.

BrowserContextSnapshot is imported from ../browser on line 13, but is redeclared here on lines 183-186. This creates a duplicate declaration. The static analysis tool also flagged this issue.

🔎 Remove the duplicate declaration:
   browserTabId?: string
 }

-export interface BrowserContextSnapshot {
-  activeTabId: string | null
-  tabs: BrowserTabInfo[]
-}
-
 export interface IYoBrowserPresenter {
src/main/presenter/browser/BrowserTab.ts-340-364 (1)

340-364: The waitForSelector polling uses requestAnimationFrame which may be throttled in background tabs.

In Electron, background tabs may have requestAnimationFrame throttled. Consider using setTimeout for more reliable polling.

🔎 Suggested fix:
     return this.evaluate(
       (sel, maxWait) =>
         new Promise<boolean>((resolve) => {
           const start = performance.now()
           const check = () => {
             if (document.querySelector(sel)) {
               resolve(true)
               return
             }
             if (performance.now() - start > maxWait) {
               resolve(false)
               return
             }
-            requestAnimationFrame(check)
+            setTimeout(check, 100)
           }
           check()
         }),
       selector,
       timeout
     )
src/main/presenter/browser/YoBrowserPresenter.ts-334-340 (1)

334-340: Missing null check on active?.contents before accessing.

The optional chaining active?.contents?.isDestroyed() returns undefined if active is null, but then the code proceeds to use active?.contents in downloadFile. Consider adding explicit validation.

🔎 Suggested fix:
   async startDownload(url: string, savePath?: string): Promise<DownloadInfo> {
     const active = await this.resolveTab()
-    if (active?.contents?.isDestroyed()) {
-      throw new Error('Active tab is destroyed')
+    if (!active || active.contents.isDestroyed()) {
+      throw new Error('No active tab available or tab is destroyed')
     }
     return await this.downloadManager.downloadFile(url, savePath, active?.contents)
   }

Committable suggestion skipped: line range outside the PR's diff.

src/main/presenter/browser/YoBrowserPresenter.ts-45-65 (1)

45-65: Consider adding a mutex/lock for ensureWindow to prevent race conditions.

If ensureWindow is called concurrently before the first call completes, multiple windows could be created. This is a potential race condition.

🔎 Suggested pattern:
+  private windowCreationPromise: Promise<number | null> | null = null
+
   async ensureWindow(): Promise<number | null> {
     const window = this.getWindow()
     if (window) return window.id

+    // Prevent concurrent window creation
+    if (this.windowCreationPromise) {
+      return this.windowCreationPromise
+    }
+
+    this.windowCreationPromise = this.createWindowInternal()
+    try {
+      return await this.windowCreationPromise
+    } finally {
+      this.windowCreationPromise = null
+    }
+  }
+
+  private async createWindowInternal(): Promise<number | null> {
     this.windowId = await this.windowPresenter.createShellWindow({
       windowType: 'browser'
     })
     // ... rest of the logic
   }

Committable suggestion skipped: line range outside the PR's diff.

src/main/presenter/browser/tools/types.ts-32-48 (1)

32-48: Naming conflict between local and shared BrowserToolDefinition types.

Two interfaces with the same name exist in different locations:

  • src/shared/types/browser.ts: Uses inputSchema: Record<string, unknown> and requiresVision
  • src/main/presenter/browser/tools/types.ts: Uses schema: ZodTypeAny, handler, and annotations

These serve different purposes (shared contracts vs. internal Zod-based tool definitions), but the identical naming creates confusion for maintainability. The BrowserContextBuilder.summarizeTools() method expects the shared version, though it's currently unused.

Rename the local type to ZodBrowserToolDefinition or InternalBrowserToolDefinition to clarify its scope and purpose.

🧹 Nitpick comments (43)
src/main/presenter/sqlitePresenter/tables/conversations.ts (6)

129-182: Add try-catch error handling to async create method.

Per coding guidelines, async methods in src/main/**/*.ts must include try-catch blocks with meaningful error messages. This method currently lacks error handling for database operations.

🔎 Apply this diff to add error handling:
   async create(title: string, settings: Partial<CONVERSATION_SETTINGS> = {}): Promise<string> {
+    try {
       const insert = this.db.prepare(`
         INSERT INTO conversations (
           conv_id,
           title,
           created_at,
           updated_at,
           system_prompt,
           temperature,
           context_length,
           max_tokens,
           provider_id,
           model_id,
           is_new,
           artifacts,
           is_pinned,
           enabled_mcp_tools,
           thinking_budget,
           reasoning_effort,
           verbosity,
           enable_search,
           forced_search,
           search_strategy,
           context_chain
         )
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
       `)
       const conv_id = nanoid()
       const now = Date.now()
       insert.run(
         conv_id,
         title,
         now,
         now,
         settings.systemPrompt || '',
         settings.temperature ?? 0.7,
         settings.contextLength || 4000,
         settings.maxTokens || 2000,
         settings.providerId || 'openai',
         settings.modelId || 'gpt-4',
         1,
         settings.artifacts || 0,
         0, // Default is_pinned to 0
         settings.enabledMcpTools ? JSON.stringify(settings.enabledMcpTools) : null,
         settings.thinkingBudget !== undefined ? settings.thinkingBudget : null,
         settings.reasoningEffort !== undefined ? settings.reasoningEffort : null,
         settings.verbosity !== undefined ? settings.verbosity : null,
         settings.enableSearch !== undefined ? (settings.enableSearch ? 1 : 0) : null,
         settings.forcedSearch !== undefined ? (settings.forcedSearch ? 1 : 0) : null,
         settings.searchStrategy !== undefined ? settings.searchStrategy : null,
         settings.selectedVariantsMap ? JSON.stringify(settings.selectedVariantsMap) : '{}'
       )
       return conv_id
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : String(error)
+      throw new Error(`Failed to create conversation "${title}": ${errorMessage}`)
+    }
   }

184-249: Add try-catch error handling to async get method.

Per coding guidelines, async methods must include try-catch blocks. The current implementation lacks error handling for potential database failures.


251-344: Add try-catch error handling to async update method.

Per coding guidelines, async methods must include try-catch blocks with meaningful error messages.


346-349: Add try-catch error handling to async delete method.

Per coding guidelines, async methods must include try-catch blocks.


351-422: Add try-catch error handling to async list method.

Per coding guidelines, async methods must include try-catch blocks.


424-433: Add try-catch error handling to async rename method.

Per coding guidelines, async methods must include try-catch blocks.

src/main/presenter/threadPresenter/handlers/searchHandler.ts.backup (5)

31-36: assertDependencies is a no-op.

The void operator evaluates its operand but doesn't validate or assert anything. This method has no effect at runtime.

If validation is intended, implement actual checks. Otherwise, remove this method.

Option 1: Implement actual assertions
 private assertDependencies(): void {
-  void this.generatingMessages
-  void this.searchingMessages
-  void this.getSearchAssistantModel
-  void this.getSearchAssistantProviderId
+  if (!this.generatingMessages) {
+    throw new Error('generatingMessages is required')
+  }
+  if (!this.searchingMessages) {
+    throw new Error('searchingMessages is required')
+  }
 }
Option 2: Remove the method entirely
-  private assertDependencies(): void {
-    void this.generatingMessages
-    void this.searchingMessages
-    void this.getSearchAssistantModel
-    void this.getSearchAssistantProviderId
-  }

And remove the call in the constructor (line 28).


99-106: Hardcoded Chinese string creates fragility and i18n issues.

The check optimizedQuery.includes('无须搜索') relies on the LLM returning an exact Chinese phrase. This is fragile and not localizable.

Consider returning a structured response (e.g., JSON with a skipSearch boolean) or using a language-agnostic marker.


166-189: Consider making the prompt language configurable.

The entire rewrite prompt is in Chinese. If the application supports multiple locales, consider externalizing this prompt or making it language-aware.


271-274: Extract magic number to a named constant.

The value 300 appears to represent an estimated average message length. A named constant would improve clarity.

Suggested fix:
+const ESTIMATED_AVG_MESSAGE_LENGTH = 300
+
 private async getContextMessages(conversationId: string): Promise<Message[]> {
   const conversation = await this.ctx.sqlitePresenter.getConversation(conversationId)
   if (!conversation) {
     throw new Error('Conversation not found')
   }

-  let messageCount = Math.ceil(conversation.settings.contextLength / 300)
+  let messageCount = Math.ceil(conversation.settings.contextLength / ESTIMATED_AVG_MESSAGE_LENGTH)
   if (messageCount < 2) {
     messageCount = 2
   }

328-348: Parallelize database writes for better performance.

Sequential await in the loop makes this O(n) in round-trip latency. For multiple search results, this can cause noticeable delays.

Suggested fix:
 private async saveSearchResults(
   messageId: string,
   results: SearchResult[],
   searchId: string
 ): Promise<void> {
-  for (const result of results) {
-    await this.ctx.sqlitePresenter.addMessageAttachment(
+  await Promise.all(
+    results.map((result) =>
+      this.ctx.sqlitePresenter.addMessageAttachment(
         messageId,
         'search_result',
         JSON.stringify({
           title: result.title,
           url: result.url,
           content: result.content || '',
           description: result.description || '',
           icon: result.icon || result.favicon || '',
           rank: typeof result.rank === 'number' ? result.rank : undefined,
           searchId
         })
       )
-  }
+    )
+  )
 }
src/main/presenter/threadPresenter/managers/searchManager.ts.backup (5)

144-154: Consider escaping selector strings to prevent script breakage.

If a selector value contains characters like backticks, ${, or the placeholder pattern itself, the generated script could break or behave unexpectedly. While hardcoded selectors are controlled, cloud-sourced selectors pass through sanitizeSelector which may not catch all problematic patterns for JavaScript string contexts.

🔎 Suggested approach
function escapeForJsString(value: string): string {
  return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/`/g, '\\`')
}

function generateExtractorScript(selectorConfig: SelectorConfig): string {
  return EXTRACTOR_SCRIPT_TEMPLATE
    .replace('{{ITEMS_SELECTOR}}', escapeForJsString(selectorConfig.itemsSelector))
    .replace('{{TITLE_SELECTOR}}', escapeForJsString(selectorConfig.titleSelector))
    // ... etc
}

271-277: Event listener is never removed, which may cause memory leaks.

The eventBus.on listener in setupEventListeners is registered but never cleaned up. If SearchManager instances are recreated, old listeners accumulate. Consider storing the listener reference and removing it in destroy().

🔎 Suggested fix
 export class SearchManager {
   private searchWindows: Map<string, BrowserWindow> = new Map()
   // ... other properties
+  private engineUpdateHandler: (() => void) | null = null

   private setupEventListeners(): void {
-    eventBus.on(CONFIG_EVENTS.SEARCH_ENGINES_UPDATED, () => {
+    this.engineUpdateHandler = () => {
       this.lastEnginesUpdateTime = 0
-    })
+    }
+    eventBus.on(CONFIG_EVENTS.SEARCH_ENGINES_UPDATED, this.engineUpdateHandler)
   }

   destroy() {
+    if (this.engineUpdateHandler) {
+      eventBus.off(CONFIG_EVENTS.SEARCH_ENGINES_UPDATED, this.engineUpdateHandler)
+    }
     // ... rest of destroy
   }
 }

575-597: Regex-based expression validation is fragile; consider a whitelist approach.

Complex regex patterns for security validation are hard to audit and may have edge cases. Consider using a strict whitelist of allowed expressions rather than pattern matching.

const ALLOWED_EXPRESSIONS = new Set([
  'titleEl.textContent',
  'titleEl.textContent?.trim()',
  'linkEl.href',
  'descEl.textContent',
  'descEl.textContent?.trim()',
  'faviconEl ? faviconEl.src : \'\'',
  "faviconEl ? faviconEl.getAttribute('src') : ''",
  "faviconEl?.src ? faviconEl.src : ''",
  "''"
])

private validateExtractExpression(expression: string): string | null {
  return ALLOWED_EXPRESSIONS.has(expression) ? expression : null
}

742-751: Polling for fullscreen exit has no maximum attempts; could loop indefinitely.

If isFullScreen() never returns false (due to OS/Electron edge cases), this creates an infinite loop. Add a maximum attempt count.

🔎 Suggested fix
       // 等待退出全屏完成
       await new Promise<void>((resolve) => {
+        let attempts = 0
+        const maxAttempts = 50 // 5 seconds max
         const checkFullScreenState = () => {
-          if (!mainWindow.isFullScreen()) {
+          if (!mainWindow.isFullScreen() || attempts >= maxAttempts) {
             resolve()
           } else {
+            attempts++
             setTimeout(checkFullScreenState, 100)
           }
         }
         checkFullScreenState()
       })

1390-1403: Async destroySearchWindow calls are not awaited in destroy().

The destroy() method calls this.destroySearchWindow(conversationId) which is async, but doesn't await the results. This could lead to incomplete cleanup if the process exits before all windows are properly destroyed.

🔎 Suggested fix
-  destroy() {
+  async destroy() {
     // 中止所有搜索操作
     for (const controller of this.abortControllers.values()) {
       controller.abort()
     }
     this.abortControllers.clear()

+    const destroyPromises: Promise<void>[] = []
     for (const [conversationId] of this.searchWindows) {
-      this.destroySearchWindow(conversationId)
+      destroyPromises.push(this.destroySearchWindow(conversationId))
     }
+    await Promise.all(destroyPromises)
+
     this.originalWindowSizes.clear()
     this.originalWindowPositions.clear()
     this.wasFullScreen.clear()
   }
src/renderer/shell/components/AppBar.vue (1)

603-620: Consider simplifying the scroll timing logic.

The nested setTimeout with nextTick pattern (lines 608-615) may be overcomplicated. The nextTick callback already waits for the next DOM update cycle, so wrapping it in a 300ms setTimeout might be unnecessary.

Consider testing if this simpler approach works:

🔎 Suggested simplification
     await yoBrowserPresenter.createTab('about:blank')
-    setTimeout(() => {
-      nextTick(() => {
-        if (endOfTabs.value) {
-          console.log('newTabButton', endOfTabs.value)
-          endOfTabs.value.scrollIntoView({ behavior: 'smooth', block: 'center' })
-        }
-      })
-    }, 300)
+    await nextTick()
+    if (endOfTabs.value) {
+      endOfTabs.value.scrollIntoView({ behavior: 'smooth', block: 'center' })
+    }

If the delay is intentionally waiting for browser tab rendering to complete, consider adding a comment explaining why the 300ms delay is needed.

src/shared/types/browser.ts (2)

20-31: Consider documenting the quality property range.

The quality property lacks documentation about its valid range (typically 0-100 for image quality). Consider adding a JSDoc comment to clarify the expected range and behavior.

🔎 Suggested improvement
 export interface ScreenshotOptions {
   fullPage?: boolean
+  /** Image quality from 0 to 100 (JPEG/PNG compression) */
   quality?: number
   selector?: string
   highlightSelectors?: string[]
   clip?: {
     x: number
     y: number
     width: number
     height: number
   }
 }

44-49: Consider more specific typing for inputSchema.

The inputSchema is typed as Record<string, unknown>, which is safe but provides minimal type information. If the project uses JSON Schema or similar, consider using a more specific type (e.g., JSONSchema7 from @types/json-schema).

This is optional since the current typing is not incorrect, just less helpful for consumers.

src/main/presenter/llmProviderPresenter/providers/geminiProvider.ts (1)

894-894: Consider removing commented-out debug code instead of leaving it.

Commented-out code tends to accumulate and become noise. If this debug logging is no longer needed, it's cleaner to remove it entirely. It can always be restored from version control if needed later.

🔎 Apply this diff to remove the dead code:
-      // console.log('chunk.candidates', JSON.stringify(chunk.candidates, null, 2))
src/main/presenter/browser/tools/screenshot.ts (1)

20-45: Consider adding error handling for screenshot capture.

While the code correctly handles the missing tab case, the tab.takeScreenshot() call at lines 31-35 lacks try-catch error handling. If the screenshot operation fails (e.g., due to DOM issues, CDP errors, or memory constraints), the error will propagate uncaught.

As per coding guidelines: "Always use try-catch to handle possible errors in TypeScript code" and "Provide meaningful error messages when catching errors."

🔎 Consider applying this error handling pattern:
       const tab = await context.getTab(tabId)
       if (!tab) {
         return {
           content: [{ type: 'text', text: `Tab ${tabId} not found` }],
           isError: true
         }
       }

+      try {
         const base64 = await tab.takeScreenshot({
           selector: parsed.selector,
           fullPage: parsed.fullPage,
           highlightSelectors: parsed.highlightSelectors
         })

         return {
           content: [
             {
               type: 'text',
               text: `data:image/png;base64,${base64}`
             }
           ]
         }
+      } catch (error) {
+        return {
+          content: [
+            {
+              type: 'text',
+              text: `Screenshot failed: ${error instanceof Error ? error.message : 'Unknown error'}`
+            }
+          ],
+          isError: true
+        }
+      }
     }
   }
 ]
src/renderer/src/stores/yoBrowser.ts (2)

27-37: Add error handling to loadState for presenter calls.

The loadState function calls yoBrowserPresenter.listTabs() and yoBrowserPresenter.isVisible() without try-catch. If either call fails, the error will propagate unhandled. Per coding guidelines, async operations should handle errors appropriately.

Suggested fix
 const loadState = async () => {
+  try {
     const [list, visible] = await Promise.all([
       yoBrowserPresenter.listTabs(),
       yoBrowserPresenter.isVisible()
     ])
     if (Array.isArray(list)) {
       tabs.value = list
       activeTabId.value = list.find((item) => item.isActive)?.id ?? null
     }
     isVisible.value = Boolean(visible)
+  } catch (error) {
+    console.error('[YoBrowser Store] Failed to load state', error)
+  }
 }

81-95: Add error handling to show, hide, and toggleVisibility actions.

These async actions call presenter methods without try-catch blocks. Failures could leave the UI in an inconsistent state.

Suggested fix for show/hide
 const show = async () => {
+  try {
     await yoBrowserPresenter.show()
     await loadState()
+  } catch (error) {
+    console.error('[YoBrowser Store] Failed to show browser', error)
+  }
 }

 const hide = async () => {
+  try {
     await yoBrowserPresenter.hide()
     await loadState()
+  } catch (error) {
+    console.error('[YoBrowser Store] Failed to hide browser', error)
+  }
 }

 const toggleVisibility = async (): Promise<boolean> => {
+  try {
     const visible = await yoBrowserPresenter.toggleVisibility()
     isVisible.value = Boolean(visible)
     return isVisible.value
+  } catch (error) {
+    console.error('[YoBrowser Store] Failed to toggle visibility', error)
+    return isVisible.value
+  }
 }
src/main/presenter/browser/ScreenshotManager.ts (1)

25-27: Consider using structured logging instead of console.error.

Per coding guidelines, main process code should use structured logging with logger.error() methods. The current implementation uses console.error, which may not integrate with the application's logging infrastructure.

src/main/presenter/llmProviderPresenter/providers/openAICompatibleProvider.ts (1)

990-990: Consider removing or enabling the debug log.

Commented-out debug logs can clutter the codebase. Consider either removing it or enabling it with a proper log level (e.g., logger.debug()).

src/renderer/shell/components/BrowserToolbar.vue (1)

127-155: Consider awaiting refreshNavigationState for better timing.

The refreshNavigationState() calls are not awaited, which means they might execute while the navigation action is still processing. This could result in stale state.

Suggested fix
 const goBack = async () => {
   if (!activeBrowserTabId.value) return
   try {
     await yoBrowserPresenter.goBack(activeBrowserTabId.value)
+    await refreshNavigationState()
   } catch (error) {
     console.error('Failed to go back', error)
   }
-  refreshNavigationState()
 }

Apply similar pattern to goForward and reload.

src/main/presenter/browser/tools/content.ts (1)

78-78: Consider adding timeout for waitForNetworkIdle.

The browser_get_markdown tool calls waitForNetworkIdle() without a timeout. On slow or perpetually-loading pages, this could block indefinitely.

🔎 Suggested fix:
-        await tab.waitForNetworkIdle()
+        await tab.waitForNetworkIdle({ timeout: 10000 })
src/main/presenter/windowPresenter/index.ts (1)

738-739: Consider extracting the type assertion to a variable for clarity.

The presenter.tabPresenter as TabPresenter cast is used multiple times throughout this file. While functionally correct, a local reference could improve readability.

🔎 Optional improvement:
     this.windows.set(windowId, shellWindow) // 将窗口实例存入 Map
-    ;(presenter.tabPresenter as TabPresenter).setWindowType(windowId, windowType)
+    const tabPresenterInstance = presenter.tabPresenter as TabPresenter
+    tabPresenterInstance.setWindowType(windowId, windowType)

This pattern is already used elsewhere in the file (e.g., line 437), so applying it here would improve consistency.

src/main/presenter/browser/BrowserToolManager.ts (1)

32-46: Consider stronger typing for args parameter and extra fallback.

Using any for args and casting extra to any reduces type safety. Since BrowserToolDefinition handlers may have specific argument schemas (via Zod), consider using unknown for args and handling the extra parameter more explicitly.

🔎 Suggested improvement
  async executeTool(
    toolName: string,
-   args: any,
+   args: unknown,
    extra?: RequestHandlerExtra<Request, Notification>
  ) {
    const tool = this.tools.find((t) => t.name === toolName)
    if (!tool) {
      return {
        content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],
        isError: true
      }
    }

    const context = this.createContext()
-   return await tool.handler(args, context, extra || ({} as any))
+   return await tool.handler(args, context, extra)
  }
src/main/presenter/browser/DownloadManager.ts (2)

41-48: Consider removing the 'updated' listener when download completes.

The updated event listener on line 41 is never explicitly removed. While Electron's DownloadItem is typically garbage-collected after the download completes, explicitly removing the listener in the done handler would be cleaner and prevent potential issues if the DownloadItem reference is retained.

🔎 Suggested improvement
+       const onUpdated = (_updateEvent, state) => {
+         const existing = this.downloads.get(id)
+         if (!existing) return
+         existing.receivedBytes = item.getReceivedBytes()
+         existing.totalBytes = item.getTotalBytes()
+         existing.status = state === 'interrupted' ? 'failed' : 'in-progress'
+         this.downloads.set(id, { ...existing })
+       }
+
-       item.on('updated', (_updateEvent, state) => {
-         const existing = this.downloads.get(id)
-         if (!existing) return
-         existing.receivedBytes = item.getReceivedBytes()
-         existing.totalBytes = item.getTotalBytes()
-         existing.status = state === 'interrupted' ? 'failed' : 'in-progress'
-         this.downloads.set(id, { ...existing })
-       })
+       item.on('updated', onUpdated)

        item.once('done', (_doneEvent, state) => {
+         item.removeListener('updated', onUpdated)
          const existing = this.downloads.get(id)
          // ... rest of done handler

5-6: Downloads map grows indefinitely without cleanup.

The downloads Map accumulates entries for every download without any cleanup mechanism. For long-running sessions with many downloads, this could lead to memory growth. Consider adding a maximum size limit or TTL-based cleanup for completed/failed downloads.

src/renderer/shell/App.vue (1)

43-47: Simplify boolean expression.

The Boolean() wrapper is unnecessary since the expression already evaluates to a boolean. The negation and && operators ensure a boolean result.

🔎 Suggested simplification
 const isWebTabActive = computed(() => {
   const tab = activeTab.value
   if (!tab) return false
-  return Boolean(!tab.url?.startsWith('local://') && tab.browserTabId)
+  return !tab.url?.startsWith('local://') && !!tab.browserTabId
 })
src/main/presenter/browser/CDPManager.ts (1)

5-14: Consider adding error handling for debugger attachment.

While isAttached() is checked before attach(), the attach() and sendCommand() calls can still throw. Consider wrapping in try-catch to provide more context on failures, per coding guidelines.

🔎 Suggested improvement
  async createSession(webContents: WebContents): Promise<Debugger> {
    const session = webContents.debugger
-   if (!session.isAttached()) {
-     session.attach('1.3')
-     await session.sendCommand('Page.enable')
-     await session.sendCommand('DOM.enable')
-     await session.sendCommand('Runtime.enable')
+   try {
+     if (!session.isAttached()) {
+       session.attach('1.3')
+       await session.sendCommand('Page.enable')
+       await session.sendCommand('DOM.enable')
+       await session.sendCommand('Runtime.enable')
+     }
+     return session
+   } catch (error) {
+     const message = error instanceof Error ? error.message : String(error)
+     throw new Error(`Failed to create CDP session: ${message}`)
    }
-   return session
  }
src/main/presenter/browser/tools/download.ts (1)

23-51: Handler ignores parsed arguments.

The browser_get_download_list handler doesn't use the args or context parameters. The tabId argument from the schema is never utilized. Consider adding a TODO comment to clarify this is intentional for the stub, or remove unused parameters from the handler signature.

-      handler: async () => {
+      handler: async (_args, _context) => {
         // Note: Download list functionality needs to be implemented in YoBrowserPresenter
-        // For now, return empty list
+        // For now, return empty list. TODO: Use tabId from args when implementing
src/main/presenter/llmProviderPresenter/managers/agentLoopHandler.ts (1)

186-198: Duplicate browser tool definition fetching logic.

This code block duplicates the logic in the getAllToolDefinitions callback (lines 30-48). The tool definitions are fetched twice per iteration: once here for passing to coreStream, and again in ToolCallProcessor via the callback.

Consider consolidating by storing the fetched definitions in a variable or refactoring getAllToolDefinitions to be reusable.

🔎 One approach - extract to a shared method:
+  private async fetchAllToolDefinitions(enabledMcpTools?: string[]): Promise<MCPToolDefinition[]> {
+    const defs: MCPToolDefinition[] = []
+    const mcpDefs = await presenter.mcpPresenter.getAllToolDefinitions(enabledMcpTools)
+    defs.push(...mcpDefs)
+
+    const hasBrowserWindow = await presenter.yoBrowserPresenter.hasWindow()
+    if (hasBrowserWindow) {
+      try {
+        const yoDefs = await presenter.yoBrowserPresenter.getToolDefinitions(
+          this.currentSupportsVision
+        )
+        defs.push(...yoDefs)
+      } catch (error) {
+        console.warn('[AgentLoop] Failed to load Yo Browser tool definitions', error)
+      }
+    }
+    return defs
+  }

Then use this.fetchAllToolDefinitions(enabledMcpTools) in both places.

src/main/presenter/browser/tools/types.ts (1)

36-37: Consider using generics instead of any for type safety.

The args: any parameter loses type information. Since schema is ZodTypeAny, you could infer the args type from it using Zod's inference utilities.

🔎 Suggested improvement:
+import type { z, ZodTypeAny } from 'zod'
+
-export interface BrowserToolDefinition {
+export interface BrowserToolDefinition<TSchema extends ZodTypeAny = ZodTypeAny> {
   name: string
   description: string
-  schema: ZodTypeAny
+  schema: TSchema
   handler: (
-    args: any,
+    args: z.infer<TSchema>,
     context: BrowserToolContext,
     extra: RequestHandlerExtra<Request, Notification>
   ) => Promise<ToolResult>
src/main/presenter/tabPresenter.ts (3)

55-67: Consider debouncing the view bounds update when chrome height changes.

When updateChromeHeight is called, it iterates over all tabs and updates their bounds synchronously. If this is called frequently (e.g., during animations), it could cause performance issues.


69-80: Missing error handling in async notification.

The notifyWindowTabsUpdate call is wrapped in .catch() but the error is only logged as a warning. The setTabBrowserId method is synchronous but triggers an async operation without proper error propagation to the caller.

🔎 Consider making this method async:
-  setTabBrowserId(tabId: number, browserTabId: string): void {
+  async setTabBrowserId(tabId: number, browserTabId: string): Promise<void> {
     const state = this.tabState.get(tabId)
     if (state) {
       state.browserTabId = browserTabId
       const windowId = this.tabWindowMap.get(tabId)
       if (windowId !== undefined) {
-        this.notifyWindowTabsUpdate(windowId).catch((error) => {
-          console.warn(`Failed to sync browser tab id for window ${windowId}:`, error)
-        })
+        await this.notifyWindowTabsUpdate(windowId)
       }
     }
   }

754-772: Magic numbers for topOffset should be documented or extracted as constants.

The hardcoded values 84 and 36 for browser vs chat window offsets are not immediately clear. Consider extracting these as named constants with documentation.

🔎 Suggested improvement:
+  // Layout constants
+  // Chat mode: AppBar = 36px (h-9)
+  // Browser mode: AppBar + BrowserToolbar = 36px + 48px = 84px (h-9 + h-12)
+  private static readonly CHAT_HEADER_HEIGHT = 36
+  private static readonly BROWSER_HEADER_HEIGHT = 84

   private updateViewBounds(window: BrowserWindow, view: WebContentsView): void {
     if (window.isDestroyed()) return
     const { width, height } = window.getContentBounds()

     const windowType = this.getWindowType(window.id)
-    const topOffset = windowType === 'browser' ? 84 : 36
+    const topOffset = windowType === 'browser' 
+      ? TabPresenter.BROWSER_HEADER_HEIGHT 
+      : TabPresenter.CHAT_HEADER_HEIGHT
     const viewHeight = Math.max(0, height - topOffset)
src/main/presenter/browser/BrowserTab.ts (2)

39-55: Consider re-throwing errors with context preservation.

The error handling logs the error but re-throws it without additional context. Consider wrapping with more context for debugging.

🔎 Suggested improvement:
     } catch (error) {
       this.status = BrowserTabStatus.Error
       console.error(`[YoBrowser][${this.tabId}] navigate failed:`, error)
-      throw error
+      throw new Error(`Navigation to ${url} failed: ${error instanceof Error ? error.message : String(error)}`)
     }

117-124: Screenshot size limit of 20000px is reasonable but consider documenting this constraint.

The 20000px limit prevents memory issues with extremely large pages, but this limitation should be documented in the method's JSDoc or interface.

src/main/presenter/browser/YoBrowserPresenter.ts (2)

187-205: Potential logic issue in navigateTab when tab is destroyed.

When the original tab is destroyed, a new tab is created with the URL. However, the method then checks if (!tab) after already having created with an ID, which could be confusing. Consider simplifying the flow.

🔎 Suggested simplification:
   async navigateTab(tabId: string, url: string, timeoutMs?: number): Promise<void> {
-    let tab = this.tabIdToBrowserTab.get(tabId)
-    if (!tab || tab.contents.isDestroyed()) {
+    const existingTab = this.tabIdToBrowserTab.get(tabId)
+    
+    if (!existingTab || existingTab.contents.isDestroyed()) {
       const created = await this.createTab(url)
       if (!created) {
         throw new Error('Failed to create tab for navigation')
       }
-      tab = this.tabIdToBrowserTab.get(created.id) ?? undefined
       this.activeTabId = created.id
+      this.emitTabNavigated(created.id, url)
+      return
     }
-    if (!tab) {
-      throw new Error(`Tab ${tabId} not found`)
-    }
-    if (tab.contents.isDestroyed()) {
-      throw new Error(`Tab ${tab.tabId} is destroyed`)
-    }
-    await tab.navigate(url, timeoutMs)
-    this.emitTabNavigated(tab.tabId, url)
+    
+    await existingTab.navigate(url, timeoutMs)
+    this.emitTabNavigated(existingTab.tabId, url)
   }

399-415: Silent catch in findReusableTab may hide URL parsing issues.

The empty catch blocks silently ignore URL parsing errors. While this is intentional for fallback behavior, consider logging at debug level for troubleshooting.

🔎 Suggested improvement:
   private findReusableTab(url: string): BrowserTab | null {
     if (!url) return this.findOldestTab()
     try {
       const targetHost = new URL(url).hostname
       const sameHost = Array.from(this.tabIdToBrowserTab.values()).find((tab) => {
         try {
           return new URL(tab.url).hostname === targetHost
         } catch {
+          // Tab URL is not a valid URL (e.g., about:blank)
           return false
         }
       })
       if (sameHost) return sameHost
     } catch {
-      // ignore parse errors
+      // Target URL is not a valid URL, fall back to oldest tab
     }
     return this.findOldestTab()
   }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/main/presenter/browser/tools/content.ts (1)

95-129: Critical: Missing error handling in all tool handlers.

All four tool handlers (browser_get_text, browser_get_markdown, browser_read_links, browser_get_clickable_elements) lack try-catch blocks around async operations and Zod schema parsing. These operations can throw on:

  • Validation failures (ContentArgsSchema.parse, etc.)
  • Tab resolution failures (context.resolveTabId, context.getTab)
  • Content extraction failures (tab.getInnerText, tab.getHtml, tab.getLinks, tab.getClickableElements)
  • Network timeouts (tab.waitForNetworkIdle)
  • HTML parsing errors (turndown.turndown)

Without error handling, exceptions will crash the handler and provide no user feedback. As per coding guidelines: always use try-catch, provide meaningful error messages, and use structured logging.

🔎 Apply this pattern to all four handlers:
       handler: async (args, context) => {
+        try {
           const parsed = ContentArgsSchema.parse(args)
           const tabId = await context.resolveTabId(parsed)
           const tab = await context.getTab(tabId)
           if (!tab) {
             return {
               content: [{ type: 'text', text: `Tab ${tabId} not found` }],
               isError: true
             }
           }

           const text = await tab.getInnerText(parsed.selector)
           const { slice, meta } = paginateText(text, parsed.offset, parsed.limit)

           const content: { type: 'text'; text: string }[] = []

           if (!slice && !meta) {
             content.push({ type: 'text', text: '(no text found)' })
           } else {
             if (meta) {
               content.push({
                 type: 'text',
                 text: `[pagination] ${meta}`
               })
             }
             if (slice) {
               content.push({
                 type: 'text',
                 text: slice
               })
             }
           }

           return { content }
+        } catch (error) {
+          // TODO: Import and use structured logger (e.g., logger.error)
+          console.error('[browser_get_text] Failed to extract text:', error)
+          const message = error instanceof Error ? error.message : String(error)
+          return {
+            content: [{ type: 'text', text: `Failed to extract text: ${message}` }],
+            isError: true
+          }
+        }
       },

Also applies to: 139-175, 184-211, 220-250

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ae98ee and 6ac71b2.

📒 Files selected for processing (1)
  • src/main/presenter/browser/tools/content.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for logs and comments (Chinese text exists in legacy code, but new code should use English)

Files:

  • src/main/presenter/browser/tools/content.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enable and maintain strict TypeScript type checking for all files

**/*.{ts,tsx}: Always use try-catch to handle possible errors in TypeScript code
Provide meaningful error messages when catching errors
Log detailed error logs including error details, context, and stack traces
Distinguish and handle different error types (UserError, NetworkError, SystemError, BusinessError) with appropriate handlers in TypeScript
Use structured logging with logger.error(), logger.warn(), logger.info(), logger.debug() methods from logging utilities
Do not suppress errors (avoid empty catch blocks or silently ignoring errors)
Provide user-friendly error messages for user-facing errors in TypeScript components
Implement error retry mechanisms for transient failures in TypeScript
Avoid logging sensitive information (passwords, tokens, PII) in logs

Files:

  • src/main/presenter/browser/tools/content.ts
src/main/presenter/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Organize core business logic into dedicated Presenter classes, with one presenter per functional domain

Files:

  • src/main/presenter/browser/tools/content.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use EventBus from src/main/eventbus.ts for main-to-renderer communication, broadcasting events via mainWindow.webContents.send()

src/main/**/*.ts: Use EventBus pattern for inter-process communication within the main process to decouple modules
Use Electron's built-in APIs for file system and native dialogs instead of Node.js or custom implementations

src/main/**/*.ts: Electron main process code belongs in src/main/ with presenters in presenter/ (Window/Tab/Thread/Mcp/Config/LLMProvider) and eventbus.ts for app events
Use the Presenter pattern in the main process for UI coordination

Files:

  • src/main/presenter/browser/tools/content.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Do not include AI co-authoring information (e.g., 'Co-Authored-By: Claude') in git commits

Files:

  • src/main/presenter/browser/tools/content.ts
**/*.{js,ts,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

Write logs and comments in English

Files:

  • src/main/presenter/browser/tools/content.ts
{src/main/presenter/**/*.ts,src/renderer/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/electron-best-practices.mdc)

Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs

Files:

  • src/main/presenter/browser/tools/content.ts
src/**/*

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

New features should be developed in the src directory

Files:

  • src/main/presenter/browser/tools/content.ts
src/main/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Main process code for Electron should be placed in src/main

Files:

  • src/main/presenter/browser/tools/content.ts
src/**/*.{ts,tsx,vue,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with single quotes, no semicolons, and 100 character width

Files:

  • src/main/presenter/browser/tools/content.ts
src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use OxLint for linting JavaScript and TypeScript files

Files:

  • src/main/presenter/browser/tools/content.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,tsx}: Use camelCase for variable and function names in TypeScript files
Use PascalCase for type and class names in TypeScript
Use SCREAMING_SNAKE_CASE for constant names

Files:

  • src/main/presenter/browser/tools/content.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use EventBus for inter-process communication events

Files:

  • src/main/presenter/browser/tools/content.ts
🧠 Learnings (10)
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Applies to src/main/presenter/llmProviderPresenter/index.ts : In `src/main/presenter/llmProviderPresenter/index.ts`, listen for standardized events yielded by `coreStream` and handle them accordingly: buffer text content (`currentContent`), handle `tool_call_start/chunk/end` events by collecting tool details and calling `presenter.mcpPresenter.callTool`, send frontend events via `eventBus` with tool call status, format tool results for the next LLM call, and set `needContinueConversation = true`

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:26:11.312Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:26:11.312Z
Learning: Applies to src/main/presenter/mcpPresenter/**/*.ts : Register new MCP tools in `mcpPresenter/index.ts` after implementing them in `inMemoryServers/`

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Implement separation of concerns where `src/main/presenter/llmProviderPresenter/index.ts` manages the Agent loop and conversation history, while Provider files handle LLM API interactions, Provider-specific request/response formatting, tool definition conversion, and native vs non-native tool call mechanisms

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Applies to src/main/presenter/llmProviderPresenter/**/*.ts : Define the standardized `LLMCoreStreamEvent` interface with fields: `type` (text | reasoning | tool_call_start | tool_call_chunk | tool_call_end | error | usage | stop | image_data), `content` (for text), `reasoning_content` (for reasoning), `tool_call_id`, `tool_call_name`, `tool_call_arguments_chunk` (for streaming), `tool_call_arguments_complete` (for complete arguments), `error_message`, `usage` object with token counts, `stop_reason` (tool_use | max_tokens | stop_sequence | error | complete), and `image_data` object with Base64-encoded data and mimeType

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:26:35.317Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-11-25T05:26:35.317Z
Learning: Applies to **/*.{ts,tsx} : Always use try-catch to handle possible errors in TypeScript code

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:26:35.317Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-11-25T05:26:35.317Z
Learning: Applies to **/*.{ts,tsx} : Do not suppress errors (avoid empty catch blocks or silently ignoring errors)

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:26:35.317Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-11-25T05:26:35.317Z
Learning: Applies to **/*.{ts,tsx} : Implement error retry mechanisms for transient failures in TypeScript

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:26:35.317Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-11-25T05:26:35.317Z
Learning: Applies to **/*.{ts,tsx} : Distinguish and handle different error types (UserError, NetworkError, SystemError, BusinessError) with appropriate handlers in TypeScript

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:26:35.317Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/error-logging.mdc:0-0
Timestamp: 2025-11-25T05:26:35.317Z
Learning: Applies to **/*.{ts,tsx} : Provide meaningful error messages when catching errors

Applied to files:

  • src/main/presenter/browser/tools/content.ts
📚 Learning: 2025-11-25T05:27:39.200Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/provider-guidelines.mdc:0-0
Timestamp: 2025-11-25T05:27:39.200Z
Learning: Applies to **/*Provider**/index.ts : Error handling: use `error` event uniformly to carry error messages, avoid mixing errors into other event fields

Applied to files:

  • src/main/presenter/browser/tools/content.ts
🧬 Code graph analysis (1)
src/main/presenter/browser/tools/content.ts (2)
src/main/presenter/browser/tools/types.ts (1)
  • BrowserToolDefinition (32-48)
src/shared/types/browser.ts (1)
  • BrowserToolDefinition (44-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/main/presenter/browser/BrowserToolManager.ts (3)

7-7: Remove commented code or add explanation.

The commented screenshot tools import suggests incomplete functionality. According to coding guidelines, unused or commented-out code should be avoided.

If screenshot functionality is intentionally disabled or not yet implemented, add a TODO comment explaining the status. Otherwise, remove the commented line.

🔎 Consider one of these approaches:

Option 1: Add explanation if temporarily disabled

-// import { createScreenshotTools } from './tools/screenshot'
+// TODO: Screenshot tools temporarily disabled pending CDP screenshot implementation
+// import { createScreenshotTools } from './tools/screenshot'

Option 2: Remove if not needed

-// import { createScreenshotTools } from './tools/screenshot'

22-22: Consistent with line 7: Remove or document commented screenshot tools.

This commented line is consistent with the commented import at line 7. Apply the same resolution (add explanation or remove entirely).


28-30: Consider defensive copy for encapsulation.

Returning the internal tools array directly allows external code to mutate it. While tool definitions are typically read-only, returning a shallow copy would provide better encapsulation.

🔎 Optional: Return a defensive copy
  getToolDefinitions() {
-   return this.tools
+   return [...this.tools]
  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac71b2 and 0f9eb28.

📒 Files selected for processing (1)
  • src/main/presenter/browser/BrowserToolManager.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for logs and comments (Chinese text exists in legacy code, but new code should use English)

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enable and maintain strict TypeScript type checking for all files

**/*.{ts,tsx}: Always use try-catch to handle possible errors in TypeScript code
Provide meaningful error messages when catching errors
Log detailed error logs including error details, context, and stack traces
Distinguish and handle different error types (UserError, NetworkError, SystemError, BusinessError) with appropriate handlers in TypeScript
Use structured logging with logger.error(), logger.warn(), logger.info(), logger.debug() methods from logging utilities
Do not suppress errors (avoid empty catch blocks or silently ignoring errors)
Provide user-friendly error messages for user-facing errors in TypeScript components
Implement error retry mechanisms for transient failures in TypeScript
Avoid logging sensitive information (passwords, tokens, PII) in logs

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/main/presenter/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Organize core business logic into dedicated Presenter classes, with one presenter per functional domain

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use EventBus from src/main/eventbus.ts for main-to-renderer communication, broadcasting events via mainWindow.webContents.send()

src/main/**/*.ts: Use EventBus pattern for inter-process communication within the main process to decouple modules
Use Electron's built-in APIs for file system and native dialogs instead of Node.js or custom implementations

src/main/**/*.ts: Electron main process code belongs in src/main/ with presenters in presenter/ (Window/Tab/Thread/Mcp/Config/LLMProvider) and eventbus.ts for app events
Use the Presenter pattern in the main process for UI coordination

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Do not include AI co-authoring information (e.g., 'Co-Authored-By: Claude') in git commits

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
**/*.{js,ts,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

Write logs and comments in English

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
{src/main/presenter/**/*.ts,src/renderer/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/electron-best-practices.mdc)

Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/**/*

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

New features should be developed in the src directory

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/main/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Main process code for Electron should be placed in src/main

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/**/*.{ts,tsx,vue,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with single quotes, no semicolons, and 100 character width

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use OxLint for linting JavaScript and TypeScript files

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,tsx}: Use camelCase for variable and function names in TypeScript files
Use PascalCase for type and class names in TypeScript
Use SCREAMING_SNAKE_CASE for constant names

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use EventBus for inter-process communication events

Files:

  • src/main/presenter/browser/BrowserToolManager.ts
🧠 Learnings (8)
📚 Learning: 2025-11-25T05:26:11.312Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:26:11.312Z
Learning: Applies to src/main/presenter/mcpPresenter/**/*.ts : Register new MCP tools in `mcpPresenter/index.ts` after implementing them in `inMemoryServers/`

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Implement separation of concerns where `src/main/presenter/llmProviderPresenter/index.ts` manages the Agent loop and conversation history, while Provider files handle LLM API interactions, Provider-specific request/response formatting, tool definition conversion, and native vs non-native tool call mechanisms

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Applies to src/main/presenter/llmProviderPresenter/index.ts : In `src/main/presenter/llmProviderPresenter/index.ts`, listen for standardized events yielded by `coreStream` and handle them accordingly: buffer text content (`currentContent`), handle `tool_call_start/chunk/end` events by collecting tool details and calling `presenter.mcpPresenter.callTool`, send frontend events via `eventBus` with tool call status, format tool results for the next LLM call, and set `needContinueConversation = true`

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Applies to src/main/presenter/llmProviderPresenter/providers/*.ts : In Provider implementations, handle native tool support by converting MCP tools to Provider format using `convertToProviderTools` and including them in the API request; for Providers without native function call support, prepare messages using `prepareFunctionCallPrompt` before making the API call

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/main/**/*.ts : Use the Presenter pattern in the main process for UI coordination

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Applies to src/main/presenter/llmProviderPresenter/providers/*.ts : In Provider implementations, include helper methods for Provider-specific operations such as `formatMessages`, `convertToProviderTools`, `parseFunctionCalls`, and `prepareFunctionCallPrompt`

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:26:11.312Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:26:11.312Z
Learning: Applies to src/main/presenter/**/*.ts : Organize core business logic into dedicated Presenter classes, with one presenter per functional domain

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
📚 Learning: 2025-11-25T05:27:12.209Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/llm-agent-loop.mdc:0-0
Timestamp: 2025-11-25T05:27:12.209Z
Learning: Applies to src/main/presenter/llmProviderPresenter/**/*.ts : Define the standardized `LLMCoreStreamEvent` interface with fields: `type` (text | reasoning | tool_call_start | tool_call_chunk | tool_call_end | error | usage | stop | image_data), `content` (for text), `reasoning_content` (for reasoning), `tool_call_id`, `tool_call_name`, `tool_call_arguments_chunk` (for streaming), `tool_call_arguments_complete` (for complete arguments), `error_message`, `usage` object with token counts, `stop_reason` (tool_use | max_tokens | stop_sequence | error | complete), and `image_data` object with Base64-encoded data and mimeType

Applied to files:

  • src/main/presenter/browser/BrowserToolManager.ts
🧬 Code graph analysis (1)
src/main/presenter/browser/BrowserToolManager.ts (3)
src/main/presenter/browser/YoBrowserPresenter.ts (1)
  • YoBrowserPresenter (20-542)
src/main/presenter/browser/tools/types.ts (2)
  • BrowserToolDefinition (32-48)
  • BrowserToolContext (8-30)
src/shared/types/browser.ts (1)
  • BrowserToolDefinition (44-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (4)
src/main/presenter/browser/BrowserTab.ts (4)

448-457: Document serialization limitations for the evaluate method.

The JSON.stringify approach (line 451-452) cannot handle circular references, functions, or certain object types. While this is a common pattern, documenting these limitations would help prevent misuse.

💡 Add JSDoc documentation:
+/**
+ * Evaluates a function in the browser context with the provided arguments.
+ * Note: Arguments must be JSON-serializable (no circular refs, functions, or symbols).
+ * @param fn - Function to evaluate in the browser context
+ * @param args - Arguments to pass (must be JSON-serializable)
+ * @returns The result of the function execution
+ */
 private async evaluate<T>(fn: (...args: any[]) => T, ...args: any[]): Promise<T> {

465-546: Consider improving type safety for event listeners.

The waitForLoad implementation is comprehensive with proper cleanup logic. However, the as any casts (lines 500, 538) suggest type mismatches with Electron's event signatures.

Consider typing the event handler to match Electron's signature:

onFailLoad: ((event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean, frameProcessId: number, frameRoutingId: number) => void) | null = null

Or use a wrapper function to avoid the cast.


39-55: Consider adding URL validation in navigate().

The navigation methods properly manage status transitions and error handling. However, navigate() doesn't validate the URL format before attempting to load, which could result in unclear error messages for invalid URLs.

💡 Optional: Add URL validation:
 async navigate(url: string, timeoutMs?: number): Promise<void> {
+  try {
+    new URL(url)
+  } catch {
+    throw new Error(`Invalid URL: ${url}`)
+  }
   this.status = BrowserTabStatus.Loading
   this.url = url
   ...

Also applies to: 136-156


290-338: Consider extracting the buildSelector utility.

The getClickableElements method includes a complex inline buildSelector function (lines 300-327). While it works correctly, extracting it to a separate utility method would improve testability and reusability.

This is a minor maintainability improvement and can be deferred if desired.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f9eb28 and 73ee886.

📒 Files selected for processing (2)
  • src/main/presenter/browser/BrowserTab.ts (1 hunks)
  • src/main/presenter/browser/DownloadManager.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for logs and comments (Chinese text exists in legacy code, but new code should use English)

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enable and maintain strict TypeScript type checking for all files

**/*.{ts,tsx}: Always use try-catch to handle possible errors in TypeScript code
Provide meaningful error messages when catching errors
Log detailed error logs including error details, context, and stack traces
Distinguish and handle different error types (UserError, NetworkError, SystemError, BusinessError) with appropriate handlers in TypeScript
Use structured logging with logger.error(), logger.warn(), logger.info(), logger.debug() methods from logging utilities
Do not suppress errors (avoid empty catch blocks or silently ignoring errors)
Provide user-friendly error messages for user-facing errors in TypeScript components
Implement error retry mechanisms for transient failures in TypeScript
Avoid logging sensitive information (passwords, tokens, PII) in logs

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/main/presenter/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Organize core business logic into dedicated Presenter classes, with one presenter per functional domain

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use EventBus from src/main/eventbus.ts for main-to-renderer communication, broadcasting events via mainWindow.webContents.send()

src/main/**/*.ts: Use EventBus pattern for inter-process communication within the main process to decouple modules
Use Electron's built-in APIs for file system and native dialogs instead of Node.js or custom implementations

src/main/**/*.ts: Electron main process code belongs in src/main/ with presenters in presenter/ (Window/Tab/Thread/Mcp/Config/LLMProvider) and eventbus.ts for app events
Use the Presenter pattern in the main process for UI coordination

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Do not include AI co-authoring information (e.g., 'Co-Authored-By: Claude') in git commits

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
**/*.{js,ts,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

Write logs and comments in English

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
{src/main/presenter/**/*.ts,src/renderer/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/electron-best-practices.mdc)

Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/**/*

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

New features should be developed in the src directory

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/main/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Main process code for Electron should be placed in src/main

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/**/*.{ts,tsx,vue,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with single quotes, no semicolons, and 100 character width

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use OxLint for linting JavaScript and TypeScript files

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,tsx}: Use camelCase for variable and function names in TypeScript files
Use PascalCase for type and class names in TypeScript
Use SCREAMING_SNAKE_CASE for constant names

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use EventBus for inter-process communication events

Files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
🧠 Learnings (3)
📚 Learning: 2025-11-25T05:26:24.867Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:26:24.867Z
Learning: Applies to {src/main/presenter/**/*.ts,src/renderer/**/*.ts} : Implement proper inter-process communication (IPC) patterns using Electron's ipcRenderer and ipcMain APIs

Applied to files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
📚 Learning: 2025-11-25T05:28:20.513Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T05:28:20.513Z
Learning: Applies to src/main/**/*.ts : Electron main process code belongs in `src/main/` with presenters in `presenter/` (Window/Tab/Thread/Mcp/Config/LLMProvider) and `eventbus.ts` for app events

Applied to files:

  • src/main/presenter/browser/DownloadManager.ts
  • src/main/presenter/browser/BrowserTab.ts
📚 Learning: 2025-11-25T05:26:24.867Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/electron-best-practices.mdc:0-0
Timestamp: 2025-11-25T05:26:24.867Z
Learning: Applies to src/main/**/*.ts : Use Electron's built-in APIs for file system and native dialogs instead of Node.js or custom implementations

Applied to files:

  • src/main/presenter/browser/DownloadManager.ts
🧬 Code graph analysis (2)
src/main/presenter/browser/DownloadManager.ts (1)
src/shared/types/browser.ts (1)
  • DownloadInfo (33-42)
src/main/presenter/browser/BrowserTab.ts (3)
src/main/presenter/browser/CDPManager.ts (1)
  • CDPManager (4-63)
src/main/presenter/browser/ScreenshotManager.ts (1)
  • ScreenshotManager (5-30)
src/shared/types/browser.ts (1)
  • ScreenshotOptions (20-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (8)
src/main/presenter/browser/DownloadManager.ts (2)

97-103: LGTM: Helper methods are clean and correct.

The listDownloads() and getDownload() methods provide straightforward read access to the downloads map. Implementation is correct.


41-64: Previous double-resolve issue has been fixed.

The earlier critical issue where the Promise resolved twice (once immediately with 'pending' status, once on completion) has been correctly addressed. The Promise now resolves only once via finalizeDownload() when the download reaches a terminal state.

src/main/presenter/browser/BrowserTab.ts (6)

1-33: LGTM: Clean class initialization.

The constructor properly initializes all necessary fields and correctly seeds URL and title from the WebContents instance.


67-134: LGTM: Robust screenshot implementation with proper cleanup.

The takeScreenshot method correctly handles multiple screenshot modes (selector, highlight, full-page, default) with appropriate error handling and cleanup. The dimension capping at 20000×20000 prevents excessive memory usage.


158-253: LGTM: User interaction methods are well-implemented.

The click, hover, fill, select, and scroll methods correctly handle DOM interactions with appropriate error messages and event dispatching. The select method properly normalizes both single and multiple values.


340-416: LGTM: Well-designed waiting utilities.

Both waitForSelector and waitForNetworkIdle are implemented with proper timeout handling, efficient polling (requestAnimationFrame), and comprehensive event listener cleanup. The network idle detection correctly tracks multiple load events and prevents double resolution.


57-65: LGTM: Clean delegation to CDP manager and evaluate helper.

These methods properly delegate to the CDPManager and internal evaluate method with appropriate session management.

Also applies to: 264-276


418-446: The data attribute naming is correct and will work as intended.

The selector at line 434 properly matches the attribute created by dataset.__deepchatHighlightIndex. Leading underscores in dataset property names follow the same transformation rules as regular camelCase properties—dashes are added before uppercase letters and everything is lowercased, so __deepchatHighlightIndex correctly becomes data-__deepchat-highlight-index. The cleanup logic will find and remove highlighted elements as expected.

Likely an incorrect or invalid review comment.

Comment on lines 66 to 73
item.on('updated', (_updateEvent, state) => {
const existing = this.downloads.get(id)
if (!existing) return
existing.receivedBytes = item.getReceivedBytes()
existing.totalBytes = item.getTotalBytes()
existing.status = state === 'interrupted' ? 'failed' : 'in-progress'
this.downloads.set(id, { ...existing })
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Memory leak: 'updated' event listener never removed.

The item.on('updated', ...) listener at line 66 is registered with .on() (not .once()), but it's never explicitly removed after the download completes or fails. If the DownloadItem object persists, this listener will remain attached indefinitely.

🔎 Fix: Remove the 'updated' listener in finalizeDownload

Store a reference to the handler and remove it when finalizing:

-        item.on('updated', (_updateEvent, state) => {
+        const updateHandler = (_updateEvent: Electron.Event, state: string) => {
           const existing = this.downloads.get(id)
           if (!existing) return
           existing.receivedBytes = item.getReceivedBytes()
           existing.totalBytes = item.getTotalBytes()
           existing.status = state === 'interrupted' ? 'failed' : 'in-progress'
           this.downloads.set(id, { ...existing })
-        })
+        }
+        item.on('updated', updateHandler)

         item.once('done', (_doneEvent, state) => {
+          item.removeListener('updated', updateHandler)
           if (state === 'completed') {
             finalizeDownload('completed')
             return
           }

           finalizeDownload('failed', state)
         })

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/presenter/browser/DownloadManager.ts around lines 66 to 73, the
'updated' listener is registered with item.on(...) and never removed, causing a
memory leak; change to store the handler in a const (e.g., const onUpdated =
(_evt, state) => { ... }) and register with item.on('updated', onUpdated), then
ensure finalizeDownload removes it (e.g., item.removeListener('updated',
onUpdated) or item.off('updated', onUpdated') ) when the download finishes/fails
so the listener is detached and the reference can be GC'd.

@zerob13 zerob13 merged commit f6aabcb into dev Dec 19, 2025
2 checks passed
@zerob13 zerob13 deleted the feat/inmem-browser-use branch January 6, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants