tabsp
dsh-web-search-chrome
Free search provider for the DeepSeek Harness web seam — drives your local Chrome via agent-browser for real Bing/Google results, no paid API. MIT.
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 15, 2026
- Updated
- Aug 15, 2026
Introduction
dsh-web-search-chrome
A free search provider for the DeepSeek Harness web capability seam (ctx.web). It drives your local Chrome through the agent-browser CLI — headless by default, with a persistent --profile directory — and needs no paid search API and no API key.
- Provider id:
local-chrome - Search engine: Bing by default (
engine: bing), Google available viaengine: google(Google is frequently gated by IP-reputation anti-bot walls on VPN/datacenter networks; Bing tolerates them far better — see docs/decisions-010.md §D7) - Model-facing
web_searchtool: zero changes — switching the seam'ssearchProvideris a one-line patch. - Every result carries
url+title+snippet(+ best-effortpublishedAt), exactly the shapedsh-webdefines.
⚠️ Privacy (read first): search terms are sent to the search engine from your machine, enter the local Chrome profile's history, and are subject to the engine's normal tracking. Searches run in a dedicated new tab that is closed when the search finishes; your existing tabs are never touched. There is no login state in 0.1 — a fresh, dedicated profile directory is used.
How it works
dsh agent's web_search tool (unchanged)
↓ ctx.web.search()
dsh-web-search-chrome (provider id: local-chrome)
↓ serialized subprocess calls
agent-browser CLI (≥ 0.34.0)
↓ --profile <path> [--headed]
local Chrome → bing.com/search?q=<query>&count=<n>&setlang=<hl> (engine: google → google.com/search)
↓ SERP extraction (in-page JS, per-engine selectors)
title / url / snippet / publishedAt → WebSearchSource[]
Per search: open a new labeled tab → wait for the DOM → extract the SERP → close the tab. Searches are strictly serialized (one browser instance, one search at a time). Results are cached: in-session (same query returns instantly) and on disk (JSON + TTL, default 24 h).
Installation
Prereq: a DeepSeek Harness (
dsh) installation. This is a dsh plugin — it runs inside dsh'sctx.webseam, so install dsh first if you have not:npm install -g @deepseek-ai/dsh(or run it ad-hoc vianpx @deepseek-ai/dsh).
Two ways — pick one:
- Manual (§1–§3 below): explicit commands, good for pinning exact options.
- Automated via a dsh agent (§4): hand a task to a dsh agent; it installs everything and writes the patch with your real paths (no placeholder edits).
1. Install the CLI and a browser
agent-browser is the only runtime dependency (it bundles/installs Chrome for Testing itself):
npm install -g agent-browser # ≥ 0.34.0
agent-browser install # downloads Chrome for Testing
- Windows (native or WSL): install agent-browser with npm on Windows; WSL can also use it through interop automatically (see Cross-platform notes below).
- Linux:
agent-browser installdownloads Chrome for Testing. If Chrome fails to launch, runagent-browser install --with-deps(system libraries) — on minimal distros installnss/nspr/alsa-liband make sure the loader can find them (see the launch check below). - macOS: install Chrome or run
agent-browser install.
🔍 Verify Chrome can actually launch —
agent-browser doctoris NOT enough. Doctor only checks that the CLI and browser binary exist; it does not load the browser, so missing shared libraries go unnoticed until the first search. Do a real launch before configuring dsh — with no agent-browser daemon running (after a reboot, orpkill -f agent-browser-linux):agent-browser open example.com # must print the URL, NOT "Chrome exited early" agent-browser close --all⚠️ Why "stop the daemon first": Chrome is spawned by the agent-browser daemon, which inherits the environment of the process that first starts it. A resident daemon can mask a missing-library problem (searches keep working until the daemon dies or the machine reboots), so the check above is only conclusive on a cold daemon.
If it fails with
error while loading shared libraries: libnspr4.so(or similar), install the libraries (nss/nspr/alsa-lib; e.g.apt install libnss3 libnspr4 libasound2on Debian/Ubuntu,pacman -S nss nspr alsa-libon Arch, or a Homebrewbrew install nss nspr alsa-lib) and make sure the process that launches dsh can find them — the daemon is first started by the dsh process on its first search. Export the library path in the shell that starts dsh, or use a wrapper:# ~/bin/dsh-web — wrapper for distros whose libs live off the loader path #!/bin/sh export LD_LIBRARY_PATH="<lib-dirs>:$LD_LIBRARY_PATH" # e.g. $(brew --prefix nss)/lib:$(brew --prefix nspr)/lib:$(brew --prefix alsa-lib)/lib exec npm exec @deepseek-ai/dsh web "$@"
2. Install the plugin into a dsh profile
dsh plugin --profile <p> add dsh-web-search-chrome
3. Patch the profile (one line switches the seam)
In <DSH_HOME>/profiles/<p>/cordis.patch.yml (e.g. ~/.dsh/profiles/web/cordis.patch.yml):
- insert:
- id: web-search-chrome
name: dsh-web-search-chrome
config:
profilePath: /home/you/.dsh-web-search-chrome/profile # ⚠️ replace /home/you with your real home
- id: web
config:
searchProvider: local-chrome # ← the one-line switch (PRD §8.1)
⚠️
profilePathmust be a real, writable path on your machine. The shippeddocs/enable-local-chrome.patch.ymluses the placeholder/home/you/...— replace it before applying (a literal/home/youdirectory will not exist and every search will fail withChrome profile not found).
profilePath is required in 0.1. Use a dedicated directory — it is created on first use. Pointing it at your real Chrome profile (e.g. Default) enables login-state reuse at your own risk; that path is officially v1.1.
⚠️ The switch takes effect on the next profile start. The profile's patch watcher hot-applies
cordis.patch.ymlto a running process, but plugin modules are cached per process — a live switch tolocal-chromecannot load into an already-running process. A ready-to-apply patch file ships atdocs/enable-local-chrome.patch.yml: copy it over<DSH_HOME>/profiles/<p>/cordis.patch.yml, then restart the profile.
Verify the composition without booting:
dsh --profile <p> --dump-config | grep -A3 searchProvider # → local-chrome
✅ Final smoke test (do this after the restart): ask the agent to run a
web_searchonce and confirm it returns real results (e.g. "Use web_search for 'deepseek harness' and report the URLs"). This exercises the full chain (tool → seam → provider → Chrome → engine); the earlier checks only prove config wiring. Requires the profile to have an agent session (the web GUI or a headless-configured profile does).
4. (Alternative) Let a dsh agent install it for you
Instead of the manual steps above, hand the task below to a dsh agent — in the web GUI chat, or one-shot headless:
dsh --profile headless "Install and enable dsh-web-search-chrome for a dsh profile.
1. Detect the platform (WSL vs native, Linux/macOS/Windows).
2. Install the agent-browser CLI if missing: 'npm install -g agent-browser', then
'agent-browser install' (add --with-deps on Linux if Chrome fails to launch).
VERIFY CHROME CAN ACTUALLY LAUNCH (doctor is not enough — it misses missing
shared libraries): run 'agent-browser open example.com' then 'agent-browser
close --all'. If it fails with 'error while loading shared libraries',
install nss/nspr/alsa-lib and make sure the dsh process finds them (export
LD_LIBRARY_PATH in the shell that starts dsh, or use a wrapper script).
3. Install the plugin: 'dsh plugin --profile <profile-name> add dsh-web-search-chrome'.
4. Write \$DSH_HOME/profiles/<profile-name>/cordis.patch.yml (default \$DSH_HOME is
~/.dsh). Use the CURRENT USER'S REAL home directory in profilePath — never a
literal /home/you:
- insert:
- id: web-search-chrome
name: dsh-web-search-chrome
config:
profilePath: <real-home>/.dsh-web-search-chrome/profile
- id: web
config:
searchProvider: local-chrome
5. Verify without booting: 'dsh --profile <profile-name> --dump-config' must show
the web-search-chrome row and 'searchProvider: local-chrome'.
6. Report to the user: the switch takes effect on the NEXT profile start (plugin
modules are cached per process), so they must restart the profile; after the
restart a web_search should return results.
Do NOT restart the profile yourself unless the user asks."
The agent performs all steps with shell access and resolves the real
profilePath itself, so the /home/you placeholder trap is avoided. Requires
the profile's agent preset to have shell tooling enabled (the standard preset
does).
⚠️ Concurrency note. The one-shot headless form boots a second dsh process on the same
$DSH_HOMEas any running profile. If the web GUI is up, prefer pasting the task into the GUI chat; if you usedsh --profile headlesswhile the GUI is running, shared storage writes can race (observed crashing the running GUI). Stop the GUI first, or give the headless run an isolatedDSH_HOME(e.g.DSH_HOME=/tmp/… dsh --profile headless "…").
Configuration
| Key | Default | Meaning |
|---|---|---|
agentBrowserPath | agent-browser | Executable: a bare name resolved through PATH, or a full path (e.g. a Windows-side exe used from WSL). |
profilePath | required | Chrome profile directory passed to --profile. Created if missing. |
engine | bing | Search engine: bing or google. Bing is the default (far more tolerant of VPN/datacenter IPs); google is available where it works. |
headed | false | Show the Chrome window (--headed) instead of headless. |
hl | en | Engine interface/result language (hl for Google, setlang for Bing). |
num | 10 | Upper bound of results requested from the engine. |
timeoutMs | 20000 | Hard per-search timeout. |
cacheTtlMs | 86400000 | Disk-cache TTL; 0 disables the disk cache (session cache always stays on). |
queueConcurrency | 1 | Serial queue — 0.1 supports exactly 1. |
cachePath | OS cache dir | Disk-cache file override (~/.cache/dsh-web-search-chrome/cache.json on POSIX, %LOCALAPPDATA%\dsh-web-search-chrome\cache.json on Windows). |
Full reference: docs/configuration.md · docs/README.md (documentation index).
Errors (English, actionable — no fallback engine)
| Situation | Message (example) |
|---|---|
| agent-browser missing | agent-browser CLI not found. Install with: npm install -g agent-browser (>= 0.34.0) |
| version too old | agent-browser 0.33.0 is too old; 0.34.0 or newer is required. Upgrade with: npm install -g agent-browser@latest |
| profile unusable | Chrome profile not found at "<path>" and could not be created: … / … is not writable. Fix permissions … |
| Chrome already running on the profile | Chrome is running. Close all Chrome windows before searching, or point profilePath at a dedicated profile directory. |
| engine captcha/anti-bot | Bing returned a captcha/consent page. Open a browser on this network and solve it once, or enable headed mode (headed: true). |
| engine consent | Bing returned a consent page. Accept the consent dialog once in a browser on this network, or enable headed mode (headed: true). |
| timeout | Search timed out after 20000ms. |
| no results | No search results found for query. |
There is deliberately no automatic fallback to another engine — a failure is reported so you can act on it (PRD §2).
Cross-platform notes
- Resolution order for the agent-browser binary: configured full path →
PATHlookup → (WSL detected) Windows npm global via interop (/mnt/c/Users/<user>/AppData/Roaming/npm/agent-browser*) → actionable error. - Windows native: install agent-browser with npm on Windows;
PATHlookup findsagent-browser.cmd. - WSL: either a Linux agent-browser on
PATH, or the Windows one through interop (both work;agentBrowserPathcan pin the full path). - macOS/Linux: plain
PATHlookup.
Tested scope (honest)
- Tested (0.1): WSL2 (Linux) end-to-end — Chrome for Testing via
agent-browser, headless +--profile, SERP extraction pipeline for both Bing (default, live E2E verified: model →web_search→ real results) and Google (fixture-verified; live on networks Google doesn't gate), cache, queue, error semantics, and the fullctx.webseam with the model-facingweb_searchtool; Windows interop binary resolution (this box'sPATHhit the Windows npm global). Also verified in a live dsh web profile (searchProvider switched tolocal-chrome, modelweb_searchreturning real Bing results through the plugin). - Not yet end-to-end tested (v1.1 per PRD): native macOS/Linux desktops and Windows-native dsh. The code paths are platform-neutral (spawn + PATH), but they are marked untested.
- Anti-bot: Bing tolerates VPN/datacenter IPs well (verified live); Google may serve its "unusual traffic" interstitial to browser traffic on flagged networks — the plugin reports it as an actionable error (
engine: googleusers: reduce frequency ≥ 30 s between searches, use a persistent profile, or route through a residential proxy; solving the interstitial once in headed mode also helps).
Development
Requirements: Node.js ≥ 22 and pnpm (the toolchain — tsdown, vitest 4, pnpm 11 — does not support older Node).
pnpm install
pnpm run build # tsdown → single-file lib/index.js + lib/index.d.ts
pnpm run typecheck # tsc --noEmit (strict)
pnpm run lint # ESLint 9 + typescript-eslint
pnpm test # vitest unit tests (self-contained)
pnpm run test:live # live engine test (needs agent-browser + browser)
pnpm run verify-extraction # runs the SERP extraction pipeline against local fixtures in real Chrome
node scripts/e2e-live.mjs bing # live engine E2E through ctx.web (bing|google)
The build shape mirrors the deepseek-harness packages: TypeScript source in
src/, one tsdown bundle (fixedExtension: false → lib/index.js) plus a
bundled lib/index.d.ts; node builtins and @deepseek-ai/* stay external.
- The SERP extraction selectors live in exactly one place:
src/serp.ts(EXTRACTION_SCRIPT, engine-adaptive — Bing#b_results/ Google#search a:has(h3)). Engine DOM changes are absorbed there. - Full documentation index: docs/README.md — configuration reference, §10 decision records (incl. the Bing-default decision §D7), and the M3 acceptance record.
License
MIT — see LICENSE. agent-browser is an external Apache-2.0 CLI; no license contamination.