Back to home

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

npm version CI License Node

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 via engine: 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_search tool: zero changes — switching the seam's searchProvider is a one-line patch.
  • Every result carries url + title + snippet (+ best-effort publishedAt), exactly the shape dsh-web defines.

⚠️ 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's ctx.web seam, so install dsh first if you have not: npm install -g @deepseek-ai/dsh (or run it ad-hoc via npx @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 install downloads Chrome for Testing. If Chrome fails to launch, run agent-browser install --with-deps (system libraries) — on minimal distros install nss/nspr/alsa-lib and 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 doctor is 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, or pkill -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 libasound2 on Debian/Ubuntu, pacman -S nss nspr alsa-lib on Arch, or a Homebrew brew 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)

⚠️ profilePath must be a real, writable path on your machine. The shipped docs/enable-local-chrome.patch.yml uses the placeholder /home/you/...replace it before applying (a literal /home/you directory will not exist and every search will fail with Chrome 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.yml to a running process, but plugin modules are cached per process — a live switch to local-chrome cannot load into an already-running process. A ready-to-apply patch file ships at docs/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_search once 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_HOME as any running profile. If the web GUI is up, prefer pasting the task into the GUI chat; if you use dsh --profile headless while the GUI is running, shared storage writes can race (observed crashing the running GUI). Stop the GUI first, or give the headless run an isolated DSH_HOME (e.g. DSH_HOME=/tmp/… dsh --profile headless "…").

Configuration

KeyDefaultMeaning
agentBrowserPathagent-browserExecutable: a bare name resolved through PATH, or a full path (e.g. a Windows-side exe used from WSL).
profilePathrequiredChrome profile directory passed to --profile. Created if missing.
enginebingSearch engine: bing or google. Bing is the default (far more tolerant of VPN/datacenter IPs); google is available where it works.
headedfalseShow the Chrome window (--headed) instead of headless.
hlenEngine interface/result language (hl for Google, setlang for Bing).
num10Upper bound of results requested from the engine.
timeoutMs20000Hard per-search timeout.
cacheTtlMs86400000Disk-cache TTL; 0 disables the disk cache (session cache always stays on).
queueConcurrency1Serial queue — 0.1 supports exactly 1.
cachePathOS cache dirDisk-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)

SituationMessage (example)
agent-browser missingagent-browser CLI not found. Install with: npm install -g agent-browser (>= 0.34.0)
version too oldagent-browser 0.33.0 is too old; 0.34.0 or newer is required. Upgrade with: npm install -g agent-browser@latest
profile unusableChrome profile not found at "<path>" and could not be created: … / … is not writable. Fix permissions …
Chrome already running on the profileChrome is running. Close all Chrome windows before searching, or point profilePath at a dedicated profile directory.
engine captcha/anti-botBing returned a captcha/consent page. Open a browser on this network and solve it once, or enable headed mode (headed: true).
engine consentBing returned a consent page. Accept the consent dialog once in a browser on this network, or enable headed mode (headed: true).
timeoutSearch timed out after 20000ms.
no resultsNo 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 → PATH lookup → (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; PATH lookup finds agent-browser.cmd.
  • WSL: either a Linux agent-browser on PATH, or the Windows one through interop (both work; agentBrowserPath can pin the full path).
  • macOS/Linux: plain PATH lookup.

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 full ctx.web seam with the model-facing web_search tool; Windows interop binary resolution (this box's PATH hit the Windows npm global). Also verified in a live dsh web profile (searchProvider switched to local-chrome, model web_search returning 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: google users: 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: falselib/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.