dsh-sidebar-human-browser
Verifieddsh-sidebar-human-browser · v0.1.0 · MIT · Web UI
Human-shaped browser control for DeepSeek Harness: Fitts-law pointer paths, press dwell, per-key typing with corrections, inertial scrolling, and a consistent fingerprint shim — drives DSH Desktop's built-in sidebar browser with no debug port.
Install
dsh plugin add dsh-sidebar-human-browser Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-sidebar-human-browser
Human-shaped browser control for DeepSeek Harness. Drives the built-in sidebar browser of DSH Desktop — the <webview> you can see — with input that looks like a person did it.
No debug port. No CDP. No external network. Everything runs in the app window.
human_open → human_snapshot → human_click / human_type / human_scroll → human_text
为什么做这个 / Why
The existing dsh-sidebar-browser-cdp is a good driver: it attaches to the sidebar <webview>, has 17 tools, and its clicks are real sendInputEvent calls (so event.isTrusted === true). What it does not do is behave like a human, and what it leaks on the environment side is enough to be filtered before behaviour even matters. Measured on DSH Desktop 0.2.1-rc.2:
| Leak | Measured value | Why it matters |
|---|---|---|
| UA | …Chrome/152… Electron/44.7.0 + @deepseek-ai/dsh-desktop/0.2.1-rc.2 |
one regex |
| UA-CH brands | [Not?A_Brand 24, Chromium 152] — no Google Chrome |
contradicts the UA string |
| Click | mouseDown → mouseUp in ~0 ms |
page can time the press |
| Typing | whole string via insertText in one tick |
zero inter-key intervals |
| Snapshot | tags the DOM with data-dsh-idx="…" |
querySelectorAll sees it |
| Scroll | one 600 px jump | no wheel ticks, no inertia |
This plugin fixes the four it can fix inside the app window, and reports the timings so you can verify it did.
改进内容 / What is different
1. Pointer: Fitts + Bezier + dwell
human_click does not teleport the cursor to the target:
- Movement time follows Fitts' law,
T = 100 + 150·log₂(D/W + 1)ms, ±15 % jitter. - Path is a quadratic Bezier with a perpendicular bow (
±18 %of the distance) and per-sample noise that shrinks near the target; sampled every 8–16 ms like a 60–120 Hz device. - 15 % of moves overshoot by a few px and correct back.
- Hover 90–380 ms before pressing; press dwell 55–260 ms (log-normal, median 110 ms); settle 60–260 ms after release.
- Every call returns
hold=…ms path=…ms (N pts)so the behaviour is measured, not assumed.
2. Keyboard: one key at a time
human_type replaces one-shot insertText with a keystroke schedule:
- per-character
keyDown→ dwell 38–115 ms →char→keyUp; - inter-key gap log-normal, median ~105 ms (clamped 45–420 ms), longer after spaces, longest after
.?!,;:; - 1.5 % typo rate, corrected with a real
Backspace(humans do this constantly — a perfect string is itself a signal); - CJK characters go through
insertTextper character with the same gaps.
3. Scrolling: inertial ticks
human_scroll splits one gesture into 4–16 wheel events with geometric decay (0.72–0.84) at 14–46 ms intervals, then a 180–620 ms settle. top/bottom use smooth scrollTo plus a reading pause.
4. Snapshot: no DOM breadcrumbs
The upstream snapshot writes data-dsh-idx attributes into the page. Any script can read them. Here the element registry lives in page memory (Map keyed by an incrementing id), so document.documentElement.outerHTML is byte-identical to what a person's browser would produce.
5. Fingerprint consistency
On dom-ready a shim aligns the identity fields that otherwise contradict each other:
navigator.userAgent,appVersion,vendor;navigator.userAgentData.brands— now[Not A(Brand 8, Chromium 152, Google Chrome 152], matching the UA;getHighEntropyValues()patched to answer consistently;navigator.webdriver = falsekept.
Configurable in cordis.patch.yml (userAgent / platform / version / fingerprint).
6. Observable by construction
The engine (lib/human.js) is pure and deterministic: pass a seed and the same path, gaps and dwells come back. Nine unit tests assert the distributions (Fitts median, dwell band, gap positivity, decay sum, drift bounds), so "it feels human" is a number, not a vibe.
安装 / Install
DSH Desktop → Plugins → Add plugin → dsh-sidebar-human-browser → Install → Enable now → 新会话
Or from a clone:
pnpm add link:$PWD # inside ~/.dsh/profiles/desktop
# add "dsh-sidebar-human-browser" to dsh.profile.bundles in that package.json
The plugin needs the sidebar browser guest to exist — human_open creates the tab.
工具 / Tools
| Tool | What it does |
|---|---|
human_open |
open a sidebar Browser tab and load a URL |
human_tabs |
list drivable tabs |
human_snapshot |
title, URL, focus state, numbered interactive elements (no DOM mutation) |
human_text |
visible page text |
human_navigate |
go to a URL and wait for the load |
human_click |
humanised click; returns measured hold/path timings |
human_type |
humanised typing; returns median key gap |
human_scroll |
inertial wheel gesture |
human_eval |
evaluate JS (least human-looking tool — prefer snapshot/text) |
真机实测 / Measured on device
同一测试页、同一 seed(42),与上游 dsh-sidebar-browser-cdp 0.3.0 对比:
| 指标 | 上游 | 本插件 | 人类基准 |
|---|---|---|---|
| 点击按下时长 | 0.5 ms | 152.6 ms | 60–220 ms |
| 点击移动次数 | 2 | 7 | 20–40 |
| 输入 keydown | 0 | 18 / 18 字符 | = 字符数 |
| 按键间隔 | 无事件 | 中位 217 ms | 50–150 ms |
| 滚动位移 | 600 px | 599 px | 连续 |
isTrusted |
true | true | true |
滚动受 Electron webview 限制,详见 docs/upstream-measurements.md。
行为数据 / Measured behaviour
npm test (deterministic seeds):
ok 1 rng is deterministic for a seed
ok 2 fittsDuration follows T = a + b*log2(D/W+1) within jitter
ok 3 mousePath ends on target and never jumps more than a few px
ok 4 clickDwell stays inside the human band and centres near 110ms
ok 5 typeSchedule: every gap positive, long text gets typos, CJK marked as ime
ok 6 keyDwell is inside 38-115ms
ok 7 scrollSequence decays and covers the requested distance
ok 8 readingPause clamps between 0.5s and 6s
ok 9 idleDrift moves no more than a couple of px per step
Example from a real run (seed fixed): hold=118ms path=612ms (17 pts).
诚实的限制 / Honest limits
- Injection timing. The shim runs at
dom-ready, notdocument-start. Fingerprint scripts that readnavigator.userAgentwhile parsing<head>see the real Electron value. Fixing this needs a preload script, i.e. a change in the app, not a plugin. - Viewport. The sidebar is ~600 px wide. A 600 px browser is itself unusual. Opening the target in a full-window tab is the physical fix; no JS shim makes a narrow window look normal without contradicting layout.
- IME.
Input.imeSetCompositionneeds CDP. CJK text is therefore inserted per character with human gaps, but the page sees nocompositionstart/update/end. Chinese-heavy sites can notice. - Focus. If the sidebar guest is not focused, key events may not land.
human_click/human_typecallwebview.focus()first, but a window that is not frontmost still reportshasFocus() === false. - This is not a bypass toolkit. It reduces behavioural signal. It does not change TLS/JA3, IP reputation, account history, or the fact that the Chromium build is Electron's.
与上游的关系 / Relation to upstream
Independent implementation, MIT, no code copied. The bridge pattern (long-poll between host tools and a client half in the app window) follows dsh-sidebar-browser-cdp by alesha-pro — credit for showing that the sidebar <webview> can be driven without a debug port. Both plugins can be installed side by side; they use different route prefixes and tool names.
License
MIT © 2026 JimmyWangJimmy