🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

侧栏浏览器操控 regrevia/dsh-inner-browser-use

DeepSeek Harness
regrevia1 天前有提交已提供中文文档扫描:含敏感能力

让 agent 在右栏浏览器里打开网页、点击输入并读取内容

30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add regrevia/dsh-inner-browser-use
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · browser
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 1 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 144 处,展示前 87 条)
  • 读取本地文件
    index.js:36
    const saved = JSON.parse(readFileSync(MIRROR_FILE, 'utf8'))
  • 写入本地文件
    index.js:46
    mkdirSync(STATE_DIR, { recursive: true })
  • 写入本地文件
    index.js:47
    writeFileSync(MIRROR_FILE, JSON.stringify(value))
  • 写入本地文件
    index.js:64
    mkdirSync(STATE_DIR, { recursive: true })
  • 写入本地文件
    index.js:65
    appendFileSync(file, `${new Date().toISOString()} ${line}\n`)
  • 读取本地文件
    index.js:66
    if (statSync(file).size <= NOTES_MAX) return
  • 读取本地文件
    index.js:67
    const whole = readFileSync(file, 'utf8')
  • 写入本地文件
    index.js:68
    writeFileSync(file, whole.slice(whole.length - NOTES_KEEP).replace(/^[^ ]* /, ''))
  • 发起网络请求
    client.js:163
    if (sent !== true) void fetch(url, { method: 'POST' })
  • 发起网络请求
    client.js:341
    fetch(`${ROUTE}/sidecar/state?session=${encodeURIComponent(who)}`)
  • 发起网络请求
    client.js:363
    void fetch(`${ROUTE}/sidecar/state?session=${encodeURIComponent(who)}`)
  • 发起网络请求
    client.js:469
    void fetch(`${ROUTE}/sidecar/claim`, {
  • 发起网络请求
    client.js:491
    void fetch(`${ROUTE}/sidecar/state?session=${encodeURIComponent(who)}`, { cache: 'no-store' })
  • 发起网络请求
    client.js:635
    fetch(`${ROUTE}/sidecar/tabs?session=${encodeURIComponent(chat)}`)
  • 写入本地文件
    review.js:100
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    review.js:110
    appendFileSync(file, `${JSON.stringify(round)}\n`)
  • 读取本地文件
    review.js:122
    if (!existsSync(file)) return null
  • 读取本地文件
    review.js:123
    const rows = readFileSync(file, 'utf8').split('\n').filter((row) => row.trim() !== '')
  • 写入本地文件
    review.js:178
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    review.js:179
    appendFileSync(file, `${JSON.stringify({ at: new Date().toISOString(), kind: 'verdict', roundAt: round.at, commit: round.commit, marks })}\n`)
  • 读取本地文件
    runtime/flash-setup.mjs:29
    return existsSync(join(path, 'Contents', 'Info.plist'))
  • 读取本地文件
    runtime/flash-setup.mjs:35
    if (noted !== undefined && existsSync(noted)) return noted
  • 读取本地文件
    runtime/flash-setup.mjs:44
    return existsSync(inner) ? inner : bundle
  • 读取本地文件
    runtime/flash-setup.mjs:56
    if (!existsSync(electron) || bundle === undefined) return null
  • 发起网络请求
    runtime/flash-setup.mjs:77
    const response = await fetch(url, { redirect: 'follow' })
  • 写入本地文件
    runtime/flash-setup.mjs:89
    mkdirSync(join(file, '..'), { recursive: true })
  • 写入本地文件
    runtime/flash-setup.mjs:100
    mkdirSync(FLASH_DIR, { recursive: true })
  • 读取本地文件
    runtime/flash-setup.mjs:103
    if (!existsSync(electronPath())) {
  • 读取本地文件
    runtime/flash-setup.mjs:105
    if (!existsSync(zip) || statSync(zip).size < 40_000_000) {
  • 删除本地文件递归
    runtime/flash-setup.mjs:117
    rmSync(staging, { recursive: true, force: true })
  • 写入本地文件
    runtime/flash-setup.mjs:118
    mkdirSync(staging, { recursive: true })
  • 执行外部命令固定命令
    runtime/flash-setup.mjs:120
    execFileSync('/usr/bin/unzip', ['-q', '-o', zip, '-d', staging], { stdio: 'ignore' })
  • 删除本地文件递归
    runtime/flash-setup.mjs:123
    rmSync(join(FLASH_DIR, 'electron'), { recursive: true, force: true })
  • 写入本地文件
    runtime/flash-setup.mjs:124
    renameSync(staging, join(FLASH_DIR, 'electron'))
  • 执行外部命令固定命令
    sidecar.js:121
    const rows = execFileSync('ps', ['-Ao', 'pid=,ppid=,rss='], { encoding: 'utf8', timeout: 3000 })
  • 读取本地文件
    sidecar.js:157
    return CHROME_BINARIES.find((path) => existsSync(path))
  • 读取本地文件
    sidecar.js:193
    return JSON.parse(readFileSync(flashFile, 'utf8')).on === true
  • 写入本地文件
    sidecar.js:207
    mkdirSync(tabsDir, { recursive: true })
  • 写入本地文件
    sidecar.js:208
    writeFileSync(flashFile, JSON.stringify({ on: value }))
  • 写入本地文件
    sidecar.js:220
    mkdirSync(dir, { recursive: true })
  • 执行外部命令固定命令
    sidecar.js:255
    parent = Number(String(execFileSync('ps', ['-o', 'ppid=', '-p', String(pid)], { encoding: 'utf8' })).trim())
  • 执行外部命令固定命令
    sidecar.js:287
    return String(execFileSync('ps', ['-o', 'command=', '-p', String(pid)], { encoding: 'utf8' })).trim()
  • 读取本地文件
    sidecar.js:349
    const said = JSON.parse(readFileSync(join(dir, 'owner.json'), 'utf8'))
  • 写入本地文件
    sidecar.js:360
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    sidecar.js:361
    appendFileSync(join(dir, 'browser.log'), `${deathNote(gone)}\n`)
  • 读取本地文件
    sidecar.js:387
    const files = COOKIE_FILES.map((name) => ({ name, from: join(dir, name) })).filter((row) => existsSync(row.from))
  • 写入本地文件
    sidecar.js:396
    copyFileSync(row.from, target)
  • 读取本地文件
    sidecar.js:403
    const mine = readdirSync(to).filter((name) => name.startsWith(`${stamp}-`))
  • 读取本地文件
    sidecar.js:404
    const older = readdirSync(to).filter((name) => !mine.includes(name)).sort()
  • 删除本地文件
    sidecar.js:407
    for (const name of older) if (!keep.includes(name)) rmSync(join(to, name), { force: true })
  • 发起网络请求
    sidecar.js:886
    const list = await fetch(`http://127.0.0.1:${this.port}/json/list`, { signal: AbortSignal.timeout(1200) })
  • 发起网络请求
    sidecar.js:904
    const list = await fetch(`http://127.0.0.1:${port}/json/list`, { signal: AbortSignal.timeout(1200) })
  • 发起网络请求
    sidecar.js:966
    if (!closed && this.port > 0) await fetch(`http://127.0.0.1:${this.port}/json/close/${target.targetId}`, { signal: AbortSignal.timeout(800) }).catch((
  • 发起网络请求
    sidecar.js:1350
    await fetch(`http://127.0.0.1:${this.flashBridge}/resize?w=${w}&h=${h}`).catch(() => {})
  • 发起网络请求
    sidecar.js:1373
    const response = await fetch(`http://127.0.0.1:${this.flashBridge}/frame.jpg`)
  • 发起网络请求
    sidecar.js:1691
    const reply = await fetch(`http://127.0.0.1:${this.flashBridge}/input`, {
  • 删除本地文件
    sidecar.js:2685
    rmSync(file, { force: true })
  • 执行外部命令来自配置/变量
    sidecar.js:2764
    const child = spawn(binary, args, { detached: false, stdio: 'ignore', env: flashEnv })
  • 执行外部命令固定命令
    sidecar.js:3172
    const guard = spawn('/bin/sh', ['-c', `while kill -0 ${app} 2>/dev/null; do sleep 5; done; kill ${chrome} 2>/dev/null`], {
  • 执行外部命令固定命令
    sidecar.js:3215
    spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' })
  • 删除本地文件
    sidecar.js:3254
    rmSync(file, { force: true })
  • 写入本地文件
    tools.js:131
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    tools.js:139
    await writeFile(file, Buffer.from(body, 'base64'))
  • 读取本地文件
    tools.js:154
    names = await readdir(dir)
  • 读取本地文件
    tools.js:161
    listed.push({ name, at: (await stat(join(dir, name))).mtimeMs })
  • 删除本地文件
    tools.js:170
    await unlink(join(dir, spare.name))
  • 发起网络请求
    tools.js:258
    const reply = await fetch(`http://127.0.0.1:${page.flashBridge}/info`, { signal: AbortSignal.timeout(600) })
  • 发起网络请求
    tools.js:3632
    const answer = await fetch(address, { headers, redirect: 'follow', signal: AbortSignal.timeout(60_000) })
  • 写入本地文件
    tools.js:3664
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    tools.js:3668
    await writeFile(file, body)
  • 读取密钥环境变量
    vault.js:39
    const given = String(process.env.DSH_INNER_BROWSER_VAULT_KEY ?? '')
  • 读取本地文件
    vault.js:47
    if (existsSync(file)) {
  • 读取本地文件
    vault.js:48
    const raw = Buffer.from(String(readFileSync(file, 'utf8')).trim(), 'base64')
  • 写入本地文件
    vault.js:54
    mkdirSync(vaultRoot(), { recursive: true })
  • 写入本地文件
    vault.js:55
    writeFileSync(file, `${made.toString('base64')}\n`, { mode: 0o600 })
  • 写入本地文件
    vault.js:56
    chmodSync(file, 0o600)
  • 执行外部命令固定命令
    vault.js:64
    const out = execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-a', 'vault', '-w'], {
  • 执行外部命令固定命令
    vault.js:80
    execFileSync('security', ['add-generic-password', '-U', '-s', KEYCHAIN_SERVICE, '-a', 'vault', '-w', key.toString('base64')], {
  • 读取密钥环境变量
    vault.js:92
    const given = String(process.env.DSH_INNER_BROWSER_VAULT_KEY ?? '')
  • 读取本地文件
    vault.js:96
    if (existsSync(file)) {
  • 读取本地文件
    vault.js:97
    const raw = Buffer.from(String(readFileSync(file, 'utf8')).trim(), 'base64')
  • 读取本地文件
    vault.js:158
    text = String(readFileSync(path, 'utf8'))
  • 写入本地文件
    vault.js:179
    writeFileSync(path, `${JSON.stringify(seal(value))}\n`, { mode: 0o600 })
  • 写入本地文件
    vault.js:181
    chmodSync(path, 0o600)
  • 写入本地文件
    vault.js:190
    mkdirSync(path, { recursive: true, mode: 0o700 })
  • 读取本地文件
    vault.js:265
    if (file === '' || !existsSync(file)) return null
  • 删除本地文件
    vault.js:304
    rmSync(file, { force: true })
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

直接操控 DeepSeek Harness 侧边栏浏览器本身:可见指针、真实点击与输入、上传下载,站点登录状态跨会话保留。

README

由 DeepSeek 最新模型翻译生成
dsh-inner-browser-use 一个由 agent 自己养的 Chrome,实时投到 DeepSeek Harness 的右栏里。 agent 打开网页、读页面、点、输入、滚动。这个浏览器是独立的 Chrome 进程,用自己的磁盘 profile,不是产品自带的那个浏览页签:右栏放的是它的画面,而画面上的鼠标仍然是你自己的。 工具 | 工具 | 作用 | | browser_open | 启动浏览器(已在跑就复用)并打开一个地址。传 headless: false 会改为在桌面上开真实窗口。 | | browser_view | 返回地址、标题、编号成 1 … 120 的控件,以及可见文字。 | | browser_read | 按人读文章的方式给 Markdown(标题、段落、列表、链接)。带 query 只回能回答的段落;问不到时自动改为整页,不再空手回来。页面和上次读的没变就只回一行,不重发正文(force: true 强制重发)。 | | browser_extract | 按 CSS 选择器把列表项读成一行一条、带链接,比整页读省得多。 | | browser_view 的 map | 大页面先看地标:各类控件各多少个、去重后的控件和编号、正文多少字,但不带回正文。 | | browser_click | 点编号控件,或点 x/y 坐标。
展开完整文档(README 剩余 1 节)
dsh-inner-browser-use

一个由 agent 自己养的 Chrome,实时投到 DeepSeek Harness 的右栏里。

agent 打开网页、读页面、点、输入、滚动。这个浏览器是独立的 Chrome 进程,用自己的磁盘 profile,不是产品自带的那个浏览页签:右栏放的是它的画面,而画面上的鼠标仍然是你自己的。

工具

| 工具 | 作用 |
| browser_open | 启动浏览器(已在跑就复用)并打开一个地址。传 headless: false 会改为在桌面上开真实窗口。 |
| browser_view | 返回地址、标题、编号成 1 … 120 的控件,以及可见文字。 |
| browser_read | 按人读文章的方式给 Markdown(标题、段落、列表、链接)。带 query 只回能回答的段落;问不到时自动改为整页,不再空手回来。页面和上次读的没变就只回一行,不重发正文(force: true 强制重发)。 |
| browser_extract | 按 CSS 选择器把列表项读成一行一条、带链接,比整页读省得多。 |
| browser_view 的 map | 大页面先看地标:各类控件各多少个、去重后的控件和编号、正文多少字,但不带回正文。 |
| browser_click | 点编号控件,或点 x/y 坐标。double: true 双击。控件被横幅或弹层挡着时,先关掉它再点。 |
| browser_type | 往焦点输入框输入文字,也可以先点一个编号控件。中文整段送进去。submit: true 回车提交。 |
| browser_key | 按一个键:Enter、Tab、Escape、Backspace、Delete、上下左右、Home、End、PageUp、PageDown。 |
| browser_scroll | 在页面中心滚轮,也可以指定 x/y。dy: 600 约一屏。 |
| browser_shot | 存一张原尺寸 PNG 到 ~/.dsh-inner-browser-use/screenshots,返回路径。 |
| browser_state | 报浏览器在不在跑、用的哪个 profile、地址、标题,以及有几个右栏在看它。 |

点、输入、按键之后的那一行会多一句结果:· navigated to …(换页了)、· content changed(页内有变化)、· no visible change(点了没动)、Flash 页则是 · screen updated / · no frame change;read: true 时不再多说。

browser_view 一次给一页文本:

https://example.com
Example Domain

1 [a] More information
7 [input] Search

编号到下一次读之前都有效。任何改动页面的操作都会顺带返回一次新的读取。

右栏

那一栏显示的是浏览器的画面,按会话自己的流发 JPEG:1440×900,质量 72,每帧都发;静止页面上约 82 KB 一帧、每秒 25 帧。

画面上有两个指针:

- 黑色箭头是 agent。 它标在 agent 上一次点击的落点上,新的点击一发出箭头就动,不等页面重绘。
- 鼠标是你自己的。 悬停、点击、滚轮、按键都当作真实输入交给浏览器,所以对拦截脚本点击的站也照常管用。移动鼠标不会带动黑色箭头。

画面上方那行地址就是地址栏:输入域名回车,页面换成它;旁边是后退、前进、刷新。你在编辑时它不会跟着页面跳。

输入分两路进来:输入法合成的文字(中文、emoji 等)整段插入,按键则按原样送过去,所以 Backspace 在页面上删掉的是一个字。

右栏只在向它要过页面的那个会话里打开。别的会话调用工具时用的是同一个浏览器,但不会抢走别人正在看的这一栏。

登录态

profile 是独立目录:~/.dsh/inner-browser-use/chrome/default。它在产品自己的浏览数据之外——那份数据是每个进程临时建的,进程一退就没了,所以在这里登过的账号第二天还在。

- 插件重载。 重载会接上仍在运行的那个 Chrome,页面也还在原处。
- Harness 重启。 重新打开同一个目录,cookie、localStorage 和 Chrome 存下的密码都留着。
- 退出。 关浏览器是请它自己关(Browser.close),Chrome 在这一步才把 cookie 落盘;空闲 15 分钟会关掉,有右栏在看时不计这段空闲。

profile 之外只写一样东西:~/.dsh-inner-browser-use/screenshots/ 里的截图。

实测

| 打开页面到第一帧 | 约 850 ms,含网络加载 |
| 单帧 | 1440×900 JPEG,约 82 KB |
| 画面 | 约 25 帧每秒 |
| 指针移动到箭头跟上 | 0.8–7 ms |
| 右栏里手动点击到画面更新 | 中位 13 ms,最坏 17 ms(点击派发 2 ms、悬停 8 ms) |

安装

git clone https://github.com/regrevia/dsh-inner-browser-use
dsh plugin install /path/to/dsh-inner-browser-use

在应用里走 插件 → 从文件夹安装,选这个仓库目录。要装打包好的 tgz,就在该目录里打一个再装:

npm pack # 得到 dsh-inner-browser-use-0.12.1.tgz
dsh plugin install ./dsh-inner-browser-use-0.12.1.tgz

装好后在 插件 里显示为 后台浏览器。机器上要有 Google Chrome 或 Chromium,细节见 docs/COMPATIBILITY.md。

使用

1. 打开你想让 agent 代跑的会话。
2. 直接说要页面上的什么事:「打开 example.com,告诉我标题」。

第一次调用会启动浏览器,并把镜像放进这个会话的右栏。没有调用就不会有东西自己打开:没要过页面的会话,右栏保持原样。

Flash 页面

browser_open 带 flash: true 时,这个地址改用插件自带的 Flash 运行时(Electron 87 + Pepper Flash)在离屏窗口里播放:窗口一次都不显示,Dock 与菜单栏都不出现,画面只在发起会话的右栏里。切换回来再调一次 flash: false,选择会记住。运行时首次使用时下载到 ~/.dsh/inner-browser-use/flash/,不需要单独安装播放器。

npm run flash-hidden 会真起一次运行时并断言「窗口未显示 / 帧持续产出 / 两帧不同」;npm run flash-mirror 量镜像帧率与按下/抬起/按键耗时;npm run flash-tools 量工具层成本(奥拉星登录页实测:点击 28 ms、输入 14 ms、browser_view 31 ms;同一页后续回话只说标题,点击 22 字符、输入 13 字符);npm run flash-local 用仓库自带的 SWF 在本机起页,不依赖网络,量首帧与输入直达。

框架、下载、网络

- iframe 能读,跨站也能。 不管是和页面共用进程,还是 Chrome 单独给一个进程,里面的控件都和页面自己的控件一样有编号;点击按框架在页面上的实际位置落点。
- 下载会落到磁盘。 点一下就下载的文件写在这个浏览器自己的 profile 目录里,browser_downloads 报出文件名、大小和到达时间。
- PDF 有去处。 Chrome 无头进程画不出 PDF,右栏只会是白图;读到 PDF 时把文件取回归档到该浏览器 profile 的 downloads/,结果里直接给绝对路径,交给 envoy_read 或本地阅读器。
- 能看到网络。 browser_network 倒序列出页面请求了什么,自己用接口填内容、屏幕上又看不出原因的页面,不用截图去猜。
- 遮罩不吞动作。 盖在按钮上的 cookie 横幅会按人的做法先关掉再点下去,同一次调用里完成;browser_do 的每一步都一样。写着删除、支付、退出的遮罩只报告,不代按。

不做什么

- 不动你自己的 Chrome。 浏览器跑在单独的 profile 上,和你正在用的窗口没有关系。
- 视口固定。 1440×900、桌面布局,不缩放,也不模拟手机。
- 一次一页。 点开新页签的链接会被跟过去:镜像落到新页面,弹窗随即关掉,不会留下第二个页面要选。

兼容性

| Harness | 0.2.0 及以上 |
| Chrome | Google Chrome、Chromium 或 Canary,112 及以上 |
| 运行时 | Node 20 及以上 |
| 许可 | MIT |

细节在 docs/COMPATIBILITY.md,两半怎么连起来在 docs/ARCHITECTURE.md。

开发

node --test test/*.test.mjs

服务端那一半是零依赖 ESM,页面那一半是运行时加载的客户端模块,都不需要构建。

许可

MIT © regrevia
预览分享卡片长什么样 ↓regrevia/dsh-inner-browser-use
分享 · DeepSeek Harness Hub
regrevia/dsh-inner-browser-use

让 agent 在右栏浏览器里打开网页、点击输入并读取内容

回到顶部