feat: Add Ripple AI browsing agent (Browse Mode + Work Mode)#228
Draft
feat: Add Ripple AI browsing agent (Browse Mode + Work Mode)#228
Conversation
Add the Ripple design document (design/RIPPLE.md) which serves as the source of truth for the AI browsing agent feature. Install @opencode-ai/sdk and @modelcontextprotocol/sdk as dependencies.
…xtension - Create FlowRippleAPI interface with methods for initialize, sessions, messages, prompts, abort, status, and event subscriptions - Define RippleEvent, RippleMessageInfo, RippleMessagePart, RippleSessionInfo, RippleStatus, and RippleMode types - Extend PageLayoutParams with rippleSidebarWidth, rippleSidebarVisible, and rippleSidebarAnimating fields for dual-sidebar layout - Add ripple: FlowRippleAPI to the global flow type declaration
- Create MCP browser server with 12 browser tools (get_page_content, navigate, click_element, type_text, scroll_page, evaluate_js, screenshot, get_page_links, get_page_inputs, etc.) that interact with tab webContents via Electron APIs - Create RippleService singleton that manages the OpenCode SDK lifecycle, session creation/management, prompt streaming, and event dispatching - Support both Browse Mode (per-tab sessions) and Work Mode sessions
- Register IPC handlers for all Ripple operations (initialize, sessions, messages, prompts, abort, status, events, toggle sidebar) - Add flow.ripple.* preload API with wrapAPI(rippleAPI, "app") permission - Import ripple IPC module in ipc/index.ts
… and menu item - Update recomputePageBounds() to compute leftWidth/rightWidth from both sidebars based on which side the main sidebar is on (Ripple always opposite) - Add independent rippleSidebarInterpolation instance for smooth animation - Register ripple.toggleSidebar shortcut (CommandOrControl+Shift+.) - Add 'Toggle Ripple Sidebar' menu item gated on rippleEnabled setting - Subscribe AppMenuController to settingsEmitter to rebuild menu on changes
- Create RippleSidebarProvider with independent React context, isEnabled gating, animation management, and resize persistence - Create RippleSidebar attached panel component with PixelBasedResizablePanel - Build chat UI: RippleSidebarInner orchestrator, MessageBubble, ToolCall, ChatMessages, ChatInput, SettingsPanel, and SessionList components - Sidebar is always on the opposite side of the main sidebar - One session per tab model for Browse Mode
- Add 5-slot ResizablePanelGroup layout (Ripple left, main sidebar left, content, main sidebar right, Ripple right) in main.tsx - Create RippleSidebarGate wrapper to read rippleEnabled setting and pass isEnabled prop to RippleSidebarProvider - Gate PresenceRippleSidebar rendering on isEnabled - Send ripple sidebar params (width, visible, animating) in BrowserContent's setLayoutParams call
- Create route config with dark theme provider - Build full-page Work Mode chat UI with collapsible session sidebar, auto-session creation, event streaming, and message rendering - Add RippleGate wrapper that checks rippleEnabled setting and shows a disabled message when Ripple is not enabled - Register flow://ripple static domain in config.ts
- Add rippleEnabled boolean setting (default: false) to BasicSettings - Create Ripple settings section with enable toggle and getting-started info (prerequisites, Browse Mode shortcut, Work Mode URL) - Add Ripple section to settings-layout with SparklesIcon and nav entry - Ripple is fully opt-in: nothing visible when disabled
Contributor
Build artifacts for all platforms are ready! 🚀Download the artifacts for: One-line installer (Unstable):bunx flow-debug-build --open 22649620553(execution 22649620553 / attempt 1) |
…n main process
The SDK only exports ESM ("import" condition, no "require"), but
electron-vite externalizes dependencies by default, causing Node's CJS
resolver to fail with ERR_PACKAGE_PATH_NOT_EXPORTED at runtime. Adding
the SDK to the externalizeDeps exclude list tells Vite to bundle it
into the main process output instead of requiring it at runtime.
…lify IPC Move session management and messaging from main process IPC to direct SDK client usage in the renderer. The main process now only handles server lifecycle (start with port:0 for random port, status, URL). - Add ripple-client.ts with singleton SDK client, model listing, and message conversion utilities - Add ModelPicker component for selecting from connected providers - Rewrite browse mode sidebar (inner.tsx) to use SDK client directly with per-tab session tracking via React refs - Rewrite work mode page (page.tsx) with SDK client, session list, and model picker in header - Remove settings-panel.tsx (fs-access toggle was non-functional) - Simplify FlowRippleAPI to 4 methods: initialize, getStatus, getServerUrl, toggleSidebar
… parts - Fix convertSdkPart() to return null for non-displayable part types (reasoning, step-finish, snapshot, patch, agent, retry, compaction, file, subtask) instead of dumping raw JSON into chat bubbles - Switch from blocking session.prompt() to promptAsync() + event.subscribe() SSE pattern for real-time streaming in both Browse and Work modes - Add Ripple system prompts so the agent identifies as 'Ripple' instead of 'opencode', with mode-appropriate behavior guidelines
…nt tool restriction - Rewrite MCP browser server HTTP handler to use stateless mode: each POST creates a fresh McpServer + StreamableHTTPServerTransport, fixing the 404 issue caused by calling mcpServer.connect() on every request - Handle GET/DELETE with proper 405 responses (not applicable in stateless mode) - Pass parsed body as third arg to transport.handleRequest() instead of Object.assign hack - Configure 'browse' and 'work' custom agents in OpenCode server config: browse agent denies edit/bash/webfetch/external_directory permissions, work agent allows all - Pass agent: 'browse' in Browse mode promptAsync calls - Pass agent: 'work' in Work mode promptAsync calls
ce3f35d to
b2cbe93
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Ripple, an opt-in AI browsing agent powered by the OpenCode SDK, with two modes:
flow://ripplewith a full-page chat interface for working on the user's desktop and filesystem.Key Design Decisions
rippleEnabledboolean setting (default:false) gates everything — sidebar, menu items, keyboard shortcuts, and the Work Mode page. Nothing about Ripple is visible when disabled.opencodeinstalled on their system. Flow detects and starts it.webContentsaccess.Changes
Dependencies
@opencode-ai/sdkand@modelcontextprotocol/sdkShared Types
FlowRippleAPIIPC interface with full session/message/event APIPageLayoutParamswith ripple sidebar fieldsMain Process
recomputePageBounds()with independentrippleSidebarInterpolationCmd/Ctrl+Shift+.to toggle Ripple sidebarrippleEnabledsettingRenderer
isEnabledgatingflow://ripplewith collapsible session sidebarSettings
rippleEnabledboolean setting (default: false)settingsEmitterPreload
flow.ripple.*API with"app"permission levelValidation
bun typecheck— passes cleanbun lint— passes cleanbun format— all files unchanged