-
Notifications
You must be signed in to change notification settings - Fork 614
feat: an Embedded Electron Browser Sandbox with Auto-Injected Tools and Environment State #1192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
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 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. 📒 Files selected for processing (2)
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Areas needing extra attention:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this 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: usenullinstead of string'NULL'.The ternary expression on Line 172 returns the string
'NULL'whenenabledMcpToolsis 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 callyoBrowserPresenter.shutdown(). Based on theIYoBrowserPresenterinterface (seesrc/shared/types/presenters/legacy.presenters.d.tslines 216-217), this presenter has ashutdown()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
browserobject 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?.datacould 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 fileLines 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 inbrowser_close_tab.The handler accesses
context.getActiveTabandcontext.resolveTabIdwithout 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 forcontext.getTab.The handler checks for
context.activateTabavailability but callscontext.getTabwithout first verifying it exists. This could cause a runtime error ifgetTabis 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 asbrowser_go_back.The
browser_go_forwardhandler 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
resolveTabIdreturns 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:BrowserContextSnapshotis already imported.
BrowserContextSnapshotis imported from../browseron 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: ThewaitForSelectorpolling usesrequestAnimationFramewhich may be throttled in background tabs.In Electron, background tabs may have
requestAnimationFramethrottled. Consider usingsetTimeoutfor 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 onactive?.contentsbefore accessing.The optional chaining
active?.contents?.isDestroyed()returnsundefinedifactiveis null, but then the code proceeds to useactive?.contentsindownloadFile. 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 forensureWindowto prevent race conditions.If
ensureWindowis 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 sharedBrowserToolDefinitiontypes.Two interfaces with the same name exist in different locations:
src/shared/types/browser.ts: UsesinputSchema: Record<string, unknown>andrequiresVisionsrc/main/presenter/browser/tools/types.ts: Usesschema: ZodTypeAny,handler, andannotationsThese 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
ZodBrowserToolDefinitionorInternalBrowserToolDefinitionto clarify its scope and purpose.
🧹 Nitpick comments (43)
src/main/presenter/sqlitePresenter/tables/conversations.ts (6)
129-182: Add try-catch error handling to asynccreatemethod.Per coding guidelines, async methods in
src/main/**/*.tsmust 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 asyncgetmethod.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 asyncupdatemethod.Per coding guidelines, async methods must include try-catch blocks with meaningful error messages.
346-349: Add try-catch error handling to asyncdeletemethod.Per coding guidelines, async methods must include try-catch blocks.
351-422: Add try-catch error handling to asynclistmethod.Per coding guidelines, async methods must include try-catch blocks.
424-433: Add try-catch error handling to asyncrenamemethod.Per coding guidelines, async methods must include try-catch blocks.
src/main/presenter/threadPresenter/handlers/searchHandler.ts.backup (5)
31-36:assertDependenciesis a no-op.The
voidoperator 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
skipSearchboolean) 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
300appears 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
awaitin 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 throughsanitizeSelectorwhich 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.onlistener insetupEventListenersis registered but never cleaned up. IfSearchManagerinstances are recreated, old listeners accumulate. Consider storing the listener reference and removing it indestroy().🔎 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 returnsfalse(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: AsyncdestroySearchWindowcalls are not awaited indestroy().The
destroy()method callsthis.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
setTimeoutwithnextTickpattern (lines 608-615) may be overcomplicated. ThenextTickcallback 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
qualityproperty 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
inputSchemais typed asRecord<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.,JSONSchema7from@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 toloadStatefor presenter calls.The
loadStatefunction callsyoBrowserPresenter.listTabs()andyoBrowserPresenter.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 toshow,hide, andtoggleVisibilityactions.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 ofconsole.error.Per coding guidelines, main process code should use structured logging with
logger.error()methods. The current implementation usesconsole.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 awaitingrefreshNavigationStatefor 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
goForwardandreload.src/main/presenter/browser/tools/content.ts (1)
78-78: Consider adding timeout forwaitForNetworkIdle.The
browser_get_markdowntool callswaitForNetworkIdle()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 TabPresentercast 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 forargsparameter andextrafallback.Using
anyforargsand castingextratoanyreduces type safety. SinceBrowserToolDefinitionhandlers may have specific argument schemas (via Zod), consider usingunknownforargsand handling theextraparameter 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
updatedevent listener on line 41 is never explicitly removed. While Electron'sDownloadItemis typically garbage-collected after the download completes, explicitly removing the listener in thedonehandler would be cleaner and prevent potential issues if theDownloadItemreference 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
downloadsMap 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 beforeattach(), theattach()andsendCommand()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_listhandler doesn't use theargsorcontextparameters. ThetabIdargument 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 implementingsrc/main/presenter/llmProviderPresenter/managers/agentLoopHandler.ts (1)
186-198: Duplicate browser tool definition fetching logic.This code block duplicates the logic in the
getAllToolDefinitionscallback (lines 30-48). The tool definitions are fetched twice per iteration: once here for passing tocoreStream, and again inToolCallProcessorvia the callback.Consider consolidating by storing the fetched definitions in a variable or refactoring
getAllToolDefinitionsto 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 ofanyfor type safety.The
args: anyparameter loses type information. SinceschemaisZodTypeAny, 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
updateChromeHeightis 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
notifyWindowTabsUpdatecall is wrapped in.catch()but the error is only logged as a warning. ThesetTabBrowserIdmethod 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
84and36for 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 innavigateTabwhen 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 havingcreatedwith 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 infindReusableTabmay 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() }
src/main/presenter/threadPresenter/handlers/searchHandler.ts.backup
Outdated
Show resolved
Hide resolved
src/main/presenter/threadPresenter/managers/searchManager.ts.backup
Outdated
Show resolved
Hide resolved
src/main/presenter/threadPresenter/managers/searchManager.ts.backup
Outdated
Show resolved
Hide resolved
src/main/presenter/threadPresenter/managers/searchManager.ts.backup
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
📒 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.tsfor main-to-renderer communication, broadcasting events viamainWindow.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 insrc/main/with presenters inpresenter/(Window/Tab/Thread/Mcp/Config/LLMProvider) andeventbus.tsfor 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
srcdirectory
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)
There was a problem hiding this 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
📒 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.tsfor main-to-renderer communication, broadcasting events viamainWindow.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 insrc/main/with presenters inpresenter/(Window/Tab/Thread/Mcp/Config/LLMProvider) andeventbus.tsfor 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
srcdirectory
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)
There was a problem hiding this 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.stringifyapproach (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 anycasts (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 = nullOr 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
buildSelectorfunction (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
📒 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.tssrc/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.tssrc/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.tssrc/main/presenter/browser/BrowserTab.ts
src/main/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use EventBus from
src/main/eventbus.tsfor main-to-renderer communication, broadcasting events viamainWindow.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 insrc/main/with presenters inpresenter/(Window/Tab/Thread/Mcp/Config/LLMProvider) andeventbus.tsfor app events
Use the Presenter pattern in the main process for UI coordination
Files:
src/main/presenter/browser/DownloadManager.tssrc/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.tssrc/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.tssrc/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.tssrc/main/presenter/browser/BrowserTab.ts
src/**/*
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
New features should be developed in the
srcdirectory
Files:
src/main/presenter/browser/DownloadManager.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/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()andgetDownload()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
waitForSelectorandwaitForNetworkIdleare 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__deepchatHighlightIndexcorrectly becomesdata-__deepchat-highlight-index. The cleanup logic will find and remove highlighted elements as expected.Likely an incorrect or invalid review comment.
| 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 }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
Summary by CodeRabbit
New Features
UI
Settings
Localization
Events
✏️ Tip: You can customize this high-level summary in your review settings.