Yingmoon0715/yingmoon-plugins

plugins/rightbar-driver

用 38 个工具从 agent 侧驱动 DSH 右推栏内置浏览器:打开、导航、读取页面正文与可交互元素、无障碍树、点击、填字、选下拉框、勾选、按键、滚动、拖动网页框边角改变大小,框内网页随之响应式重排,框宽收窄即呈现手机版布局、截图、整页截图、导出 PDF、导出 markdown、按选择器提取 JSON、下载文件、存取 Cookie,以及带序号的操作记录、重放与动作白名单。同时把内置 web_search 接到自建 SearXNG(经 SSH 隧道,Tavily 与真实浏览器作兜底),并注册抓取提供者:被墙的 GitHub 地址按实测可用的镜像顺序重试,并从响应头嗅探字符集,使 GBK 与 GB2312 页面正确解码。

浏览器与网页deepseek-harnessdshyingmoon

rightbar-driver

Drive the built-in browser inside the DeepSeek Harness right sidebar.

What it does

The DSH right sidebar ships a browser tab (Ctrl+T, the official ui-sidebar-browser). It renders pages fine, but nothing could operate it from the agent side — you had to click and type by hand.

This plugin makes that browser operable. 38 tools, grouped by what they do:

Open and navigate

  • rightbar_open — open the sidebar browser tab, optionally with a URL
  • rightbar_navigate — navigate to a URL
  • rightbar_nav — back, forward, reload
  • rightbar_history — what was opened before, persisted across sessions and restarts
  • rightbar_tabs — list, switch, close tabs

Observe

  • rightbar_read — page text plus interactive elements carrying dsh-ref-N handles
  • rightbar_a11y — the accessibility tree: semantic roles, names, states. Cheaper on context
  • rightbar_controls — form controls with their current values and refs
  • rightbar_captcha — locate CAPTCHAs and say which kind
  • rightbar_info — address, title, load state, scroll position
  • rightbar_shot — screenshot to disk, returns the path
  • rightbar_shot_full — full-page screenshot, top to bottom in one image
  • rightbar_health — crash, loading, mute status

Operate

  • rightbar_click — click by ref or selector, or by x/y coordinates
  • rightbar_fill — type into an input
  • rightbar_type — type text character by character
  • rightbar_set_value — set or clear by ref, selector, or label text
  • rightbar_select — pick from a dropdown
  • rightbar_check — tick or untick checkboxes and radios
  • rightbar_key — press keys (Enter, Tab, arrows, Ctrl+A — real key events on desktop)
  • rightbar_scroll — scroll the page

Frame size

  • rightbar_resize — resize the browser frame. handle drops a drag grip on the frame's

bottom-right corner; a pixel width sets it directly; presets phone(375) / narrow(480) / wide(720) / wider(960) switch quickly; reset restores the official default. Narrow the frame and the page's responsive layout reflows to its mobile shape on its own.

View and keep

  • rightbar_zoom — engine-level zoom, not font size
  • rightbar_find — find text in the page
  • rightbar_pdf — export the current page to PDF
  • rightbar_style — inject CSS to change font size, hide overlays, go dark
  • rightbar_eval — run a script in the page and bring back the result

Extract and download

  • rightbar_scrape — pull a list into JSON by CSS selector
  • rightbar_download — download a file to the host
  • rightbar_content — export the whole page as markdown, html, text, or json

Login state and teardown

  • rightbar_cookies — save or restore cookies
  • rightbar_session_reset — rebuild the browser session
  • rightbar_close — close the browser tab

Record and guardrails

  • rightbar_ops — every operation with sequence, time, duration, outcome
  • rightbar_replay — replay actions, by sequence, range, or last N
  • rightbar_restrict — whitelist actions (readonly mode)
  • rightbar_search_status — which route the built-in web search is taking right now

Opening and navigation go through the official public API ctx.sidebarRight.openTab('browser', { params: { url } }). Reading and interaction run inside the browser guest through the same channel the official desktop build uses. No extra process, no second browser, no memory overhead, and nothing touches the browser you are already logged into.

Why it exists

Every other browser plugin on the market starts a *second* browser: a Playwright Chromium, an Electron window, or a CDP connection to your own Chrome. That is the right answer when you need isolation, downloads, or cookie persistence. It is the wrong answer when the browser you want is already sitting in the sidebar and you just want the agent to be able to use it.

Requirements

  • DSH desktop build. The sidebar browser renders as an Electron `` there,

which is what makes page reading possible.

  • A Session must be on screen. The official sidebarRight.require() throws

"no session surface is mounted" otherwise — that is official behaviour, not a plugin limitation.

  • On dsh web (no desktop shell) the sidebar browser renders as an ``.

Opening and navigation still work; reading and interaction cannot, because the browser same-origin policy forbids reading a cross-origin frame. The plugin reports that plainly instead of throwing an opaque error.

Install

dsh plugin add github:Yingmoon0715/yingmoon-plugins#path:/plugins/rightbar-driver

Or from the plugin market inside DSH.

It also replaces web search

DeepSeek's official web search becomes pay-per-call on 1 November 2026. This plugin also registers itself as a DSH search provider (id moon-local), so web_search runs on your own sources instead of the metered official one.

Three tiers, first success wins:

  1. Self-hosted SearXNG (recommended): reached over an SSH tunnel the plugin

opens on demand and reopens after a drop. Configuration lives in DSH_HOME/search-sources.json.

  1. Tavily: only used when tavilyKey is set.
  2. Real browser fallback: opens Sogou, 360, then Bing in the sidebar browser and

scrapes real result links. Last resort — it does touch sidebar tabs.

All three failing raises an error that names each tier's reason.

One rule worth keeping: provider availability must be a cheap check, never a network probe on every call. The plugin keeps an in-memory ledger — after all three tiers fail it reports unavailable for a 60-second cooldown, so the harness stops routing searches into a dead end.

Fetch that goes around blocks

Search is only half of it: a result you cannot open is not a result. This plugin also registers a fetch provider (moon-fetch) that keeps a mirror table and retries through it whenever the direct request fails or returns a non-2xx.

  • Covers github.com, raw.githubusercontent.com, codeload.github.com, and

api.github.com through ghfast.top / gh-proxy.com, with kkgithub and bgithub as last resort.

  • Mirror order is measured, not guessed: on 2026-10-26 the first two returned 200,

kkgithub failed to connect, and bgithub returned 403 — so the working pair runs first.

  • Charset is sniffed from the header and the first 4 KB before decoding, so GBK and

GB2312 Chinese pages do not come back garbled.

  • Errors name every address that was tried and why each one failed.

Registering a second fetch provider makes the harness see more than one available provider, so pin it in the profile patch:

- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: moon-local
    fetchProvider: moon-fetch

Leave fetchProvider: http to keep the official fetcher.

Self-test: /rightbar-api/fetch-test?u= reports the final URL, status, length, and the first 300 characters.

Self-test: /rightbar-api/search-test?q=term runs through DSH's own ctx.web.search and reports lastVia, lastCount, lastError, and downUntil.

Status

38 tools. The environment is stated honestly rather than passing clone results off as desktop results.

Verified on the Web clone (dsh web, sidebar browser is an `): open returns frames: 1; the whole resize path; tabs, info, health, ops, zoom, wait, find, style, eval; and the rejection path of scrape and history`.

Verified on desktop (Electron webview): an earlier 6-tool build was exercised one by one on the author's own DSH 0.2.1-rc.2 desktop — open returned frames: 1, read returned real page content (URL, title, 31 interactive elements, multilingual body), navigate went through webview.loadURL and really changed the URL, click really navigated, shot and shot_full wrote real screenshots. The 38-tool build has not been re-verified on desktop as a whole — that is the next step once a restart is possible.

Cannot be verified on the Web clone (browser same-origin policy, not a plugin defect): read, click, fill, type, set_value, select, check, key, scrape, download, cookies, a11y, controls, captcha, pdf, content all need to read inside the browser guest, and a cross-origin iframe refuses by design. Those calls return an explicit same-origin message rather than a result. On desktop the guest is a ` reached through executeJavaScript`, which is not subject to that limit.

Official assumptions checked before shipping — from the author's own app.asar build (0.2.1-rc.3), item by item rather than inferred: the data-sidebar-browser-frame attribute, createElement("webview"), host.append(element), the dshDesktop.protocolVersion === 1 branch, loadURL, dom-ready, and openTab are all present and unchanged. CSS class hashes differ between the asar build and the npm package (bZwPEG_* vs SB_kFW_*), but this plugin never references official class names — only tag names, that data attribute, and public APIs.

resize measured on the clone (direct calls to the real functions plus the full chain). The frame itself was measured directly, and the page inside was measured by the page itself: a test page reports its own window.innerWidth and which media query matched back to the parent via postMessage. Screenshots are unavailable on the Web build — rightbar_shot states plainly that an iframe has no capture interface — so this is harder evidence than a picture anyway: the numbers come from the page.

frame 745 (default) -> page viewport 745 -> desktop layout frame 375 / 480 / 600 -> page viewport 375 / 480 / 600 -> mobile layout (media query max-width:600px matched) frame 720 / 960 / 1200 -> same -> desktop layout reset -> page viewport 745 -> desktop layout

Frame width and page viewport match pixel for pixel at every step, with 600 as the boundary. What this means in practice: drag the frame below 600 and the page shows its mobile layout; drag it wider and it goes back to desktop. That reflow is the point of the feature, and it is measured, not assumed. the drag grip sits flush with the frame's bottom-right corner, gap 0 on both edges; setting width 600 moves the frame from 745 to 600; a simulated 120 px mouse move widens the frame by 120; reset returns to 745 leaving no inline residue; presets phone 375 / narrow 480 / wide 720 / wider 960 all exact; an illegal width is rejected with the preset list.

One implementation note: the official build puts the browser frame in a flex chain — the frame and every ancestor container are flex:1 1 auto, so the width comes out of the flex algorithm and writing width alone does nothing. resize uses width + flex:none + max-width together to take the frame out of flex sizing.

Search side: against the self-hosted SearXNG a CJK query returned five real results in 2.5 s; pointed at a dead port it failed in 3 ms and then reported unavailable, instead of stalling the whole call.

Two real defects found by dragging it for real

Both of these were invisible to the earlier verification, which only set widths and dispatched synthetic mouse events. They only showed up when the frame was dragged with real mouse input in both directions, which is the thing the feature is for.

1. Dragging wider than the viewport made the handle unreachable. The viewport is 746 px and the default frame is 745, so it fills the screen exactly. With no upper bound, asking for 900 px put the handle centre at 892 — 146 px outside the viewport, where document.elementFromPoint returns null. The user drags a little too far, the handle vanishes, and there is no way to drag it back except reset. Fixed by clamping to the room the container actually has (viewport right - frame left - 2), applied in one place so presets, pixel values and dragging all obey it.

2. Dragging left did nothing at all. Moving the mouse leftwards takes it over the iframe — the web page — and the mousemove events are swallowed by that document, so a listener on window never fires. Dragging rightwards worked only because the mouse leaves the frame and stays in the parent document. setPointerCapture was tried first and measured not to help. The fix is a full-screen transparent shield created on mousedown: the mouse never enters the iframe during a drag, so the events stay in the parent document. The shield is removed on mouseup, and also on window blur and document mouseleave so it can never get stuck covering the interface.

Measured after both fixes, with a real page loaded into the frame and real mouse events through CDP:

drag left 100 745 -> 645 drag right 100 645 -> 743 (clamped to the ceiling) drag left 250 743 -> 493 drag right 400 493 -> 743 (clamped) request 900 743 request 4000 743 reset 745

The handle is grabbable at every step and no shield is left behind.

Defects fixed along the way

Listed rather than hidden:

  1. A missing await in the dispatch turned the tool result into an empty object, which

looked like "called it, nothing happened".