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

浏览器统一驱动 Short-Arm-Ape/dsh-browser-unified

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

DeepSeek Harness(dsh)浏览器插件

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

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

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装;安装清单取自 packages/browser-unified-plugin/package.json。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Short-Arm-Ape/dsh-browser-unified
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

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

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装;安装清单取自 packages/browser-unified-plugin/package.json

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-browser-unified(未发布到 npm,仅可源码安装)
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/3 19:28:53

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 95 处,展示前 83 条)
  • 发起网络请求
    extension/background.js:184
    const res = await fetch(`http://127.0.0.1:${port}/api/status`, {
  • 读取本地文件
    packages/browser-unified-mit/lib/tools.js:42
    const parsed = JSON.parse(await readFile(file, 'utf8'));
  • 执行外部命令固定命令
    packages/browser-unified-mit/lib/tools.js:49
    execFile('git', args, { timeout: 25_000, maxBuffer: 1 << 20, windowsHide: true }, (error, stdout, stderr) => {
  • 发起网络请求
    packages/browser-unified-mit/lib/tools.js:259
    const outcome = await approval.request({ agent: { id: agent.id }, toolName: 'browser_design_edit', reason, signal: exec.signal });
  • 读取本地文件
    packages/browser-unified-mit/lib/tools.js:263
    const before = await readFile(registryFile, 'utf8');
  • 写入本地文件
    packages/browser-unified-mit/lib/tools.js:264
    await copyFile(registryFile, registryFile + '.bak');
  • 写入本地文件
    packages/browser-unified-mit/lib/tools.js:266
    await writeFile(registryFile, JSON.stringify(reg, null, 2) + '\n', 'utf8');
  • 读取本地文件
    packages/browser-unified-mit/src/tools.ts:47
    const parsed: unknown = JSON.parse(await readFile(file, 'utf8'))
  • 执行外部命令固定命令
    packages/browser-unified-mit/src/tools.ts:55
    execFile('git', args as string[], { timeout: 25_000, maxBuffer: 1 << 20, windowsHide: true }, (error, stdout, stderr) => {
  • 发起网络请求
    packages/browser-unified-mit/src/tools.ts:261
    const outcome = await approval.request({ agent: { id: agent.id }, toolName: 'browser_design_edit', reason, signal: (exec as { signal?: AbortSignal }).
  • 读取本地文件
    packages/browser-unified-mit/src/tools.ts:266
    const before = await readFile(registryFile, 'utf8')
  • 写入本地文件
    packages/browser-unified-mit/src/tools.ts:267
    await copyFile(registryFile, registryFile + '.bak')
  • 写入本地文件
    packages/browser-unified-mit/src/tools.ts:269
    await writeFile(registryFile, JSON.stringify(reg, null, 2) + '\n', 'utf8')
  • 读取本地文件
    packages/browser-unified-plugin/lib/browser-profiles.js:170
    if (existsSync(local))
  • 读取本地文件
    packages/browser-unified-plugin/lib/browser-profiles.js:176
    if (existsSync(appPath))
  • 读取本地文件
    packages/browser-unified-plugin/lib/browser-profiles.js:183
    if (existsSync(candidate))
  • 读取本地文件
    packages/browser-unified-plugin/lib/browser-profiles.js:239
    return readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
  • 执行外部命令固定命令
    packages/browser-unified-plugin/lib/browser-profiles.js:280
    const out = execFileSync('cmd.exe', ['/c', `echo %${name}%`], {
  • 读取本地文件
    packages/browser-unified-plugin/lib/browser-profiles.js:432
    if (extensionId === '' || !existsSync(userDataDirLocal))
  • 读取本地文件
    packages/browser-unified-plugin/lib/browser-profiles.js:435
    if (!existsSync(profilePath))
  • 发起网络请求
    packages/browser-unified-plugin/lib/buddy.js:338
    const res = await fetch(`http://127.0.0.1:${port}/api/status`, {
  • 读取本地文件
    packages/browser-unified-plugin/lib/buddy.js:462
    const raw = await readFile(marksFile(), 'utf8');
  • 写入本地文件
    packages/browser-unified-plugin/lib/buddy.js:481
    await mkdir(path.dirname(marksFile()), { recursive: true });
  • 写入本地文件
    packages/browser-unified-plugin/lib/buddy.js:482
    await writeFile(marksFile(), JSON.stringify({ version: 1, sessions: state.marks.sessions }, null, 2) + '\n', 'utf8');
  • 发起网络请求
    packages/browser-unified-plugin/lib/client.js:560
    fetch(UPDATE_API, { headers: { accept: 'application/vnd.github+json' } })
  • 发起网络请求
    packages/browser-unified-plugin/lib/client.js:827
    return fetch('http://127.0.0.1:' + port + '/api/status', {
  • 读取本地文件
    packages/browser-unified-plugin/lib/index.js:338
    const raw = JSON.parse(readFileSync(this.pairingFile(), 'utf8'));
  • 写入本地文件
    packages/browser-unified-plugin/lib/index.js:351
    mkdirSync(path.dirname(this.pairingFile()), { recursive: true });
  • 写入本地文件
    packages/browser-unified-plugin/lib/index.js:352
    writeFileSync(this.pairingFile(), `${JSON.stringify(record, undefined, '\t')}\n`, 'utf8');
  • 读取本地文件
    packages/browser-unified-plugin/lib/index.js:441
    if (existsSync(local)) {
  • 读取本地文件
    packages/browser-unified-plugin/lib/index.js:724
    if (looksLikePath && !existsSync(exe)) {
  • 执行外部命令来自配置/变量
    packages/browser-unified-plugin/lib/index.js:729
    const child = spawn(exe, args, { detached: true, stdio: 'ignore' });
  • 发起网络请求
    packages/browser-unified-plugin/lib/index.js:1093
    const outcome = await approval.request({
  • 写入本地文件
    packages/browser-unified-plugin/lib/index.js:1216
    await mkdir(dir, { recursive: true });
  • 写入本地文件
    packages/browser-unified-plugin/lib/index.js:1221
    await writeFile(file, buffer);
  • 写入本地文件
    packages/browser-unified-plugin/lib/index.js:1239
    await mkdir(path.dirname(file), { recursive: true });
  • 写入本地文件
    packages/browser-unified-plugin/lib/index.js:1242
    await mkdir(dir, { recursive: true });
  • 读取本地文件
    packages/browser-unified-plugin/lib/index.js:2830
    const pkg = JSON.parse(readFileSync(path.join(here, '..', 'package.json'), 'utf8'));
  • 读取本地文件
    packages/browser-unified-plugin/lib/index.js:2845
    const doc = JSON.parse(readFileSync(file, 'utf8'));
  • 读取本地文件
    packages/browser-unified-plugin/lib/permissions.js:98
    stat = statSync(file);
  • 读取本地文件
    packages/browser-unified-plugin/lib/permissions.js:109
    parsed = JSON.parse(readFileSync(file, 'utf8'));
  • 读取本地文件
    packages/browser-unified-plugin/lib/unified-tools.js:97
    const parsed = JSON.parse(await readFile(file, 'utf8'));
  • 执行外部命令固定命令
    packages/browser-unified-plugin/lib/unified-tools.js:104
    execFile('git', args, { timeout: 25_000, maxBuffer: 1 << 20, windowsHide: true }, (error, stdout, stderr) => {
  • 发起网络请求
    packages/browser-unified-plugin/lib/unified-tools.js:207
    outcome = await approval.request({ agent, toolName, reason, signal: exec.signal });
  • 读取本地文件
    packages/browser-unified-plugin/lib/unified-tools.js:411
    const before = await readFile(registryFile, 'utf8');
  • 写入本地文件
    packages/browser-unified-plugin/lib/unified-tools.js:412
    await copyFile(registryFile, registryFile + '.bak');
  • 写入本地文件
    packages/browser-unified-plugin/lib/unified-tools.js:414
    await writeFile(registryFile, JSON.stringify(reg, null, 2) + '\n', 'utf8');
  • 写入本地文件
    packages/browser-unified-plugin/lib/unified-tools.js:553
    await copyFile(file, file + '.bak');
  • 写入本地文件
    packages/browser-unified-plugin/lib/unified-tools.js:554
    await writeFile(file, JSON.stringify(reg, null, 2) + '\n', 'utf8');
  • 读取本地文件
    packages/browser-unified-plugin/src/browser-profiles.ts:206
    if (existsSync(local)) out.push(local)
  • 读取本地文件
    packages/browser-unified-plugin/src/browser-profiles.ts:210
    if (existsSync(appPath)) out.push(appPath)
  • 读取本地文件
    packages/browser-unified-plugin/src/browser-profiles.ts:215
    if (existsSync(candidate)) out.push(candidate)
  • 读取本地文件
    packages/browser-unified-plugin/src/browser-profiles.ts:262
    return readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')
  • 执行外部命令固定命令
    packages/browser-unified-plugin/src/browser-profiles.ts:301
    const out = execFileSync('cmd.exe', ['/c', `echo %${name}%`], {
  • 读取本地文件
    packages/browser-unified-plugin/src/browser-profiles.ts:469
    if (extensionId === '' || !existsSync(userDataDirLocal)) return undefined
  • 读取本地文件
    packages/browser-unified-plugin/src/browser-profiles.ts:471
    if (!existsSync(profilePath)) return undefined
  • 发起网络请求
    packages/browser-unified-plugin/src/buddy.ts:347
    const res = await fetch(`http://127.0.0.1:${port}/api/status`, {
  • 读取本地文件
    packages/browser-unified-plugin/src/buddy.ts:469
    const raw = await readFile(marksFile(), 'utf8');
  • 写入本地文件
    packages/browser-unified-plugin/src/buddy.ts:487
    await mkdir(path.dirname(marksFile()), { recursive: true });
  • 写入本地文件
    packages/browser-unified-plugin/src/buddy.ts:488
    await writeFile(marksFile(), JSON.stringify({ version: 1, sessions: state.marks.sessions }, null, 2) + '\n', 'utf8');
  • 读取本地文件
    packages/browser-unified-plugin/src/index.ts:549
    const raw = JSON.parse(readFileSync(this.pairingFile(), 'utf8')) as PairingRecord
  • 写入本地文件
    packages/browser-unified-plugin/src/index.ts:562
    mkdirSync(path.dirname(this.pairingFile()), { recursive: true })
  • 写入本地文件
    packages/browser-unified-plugin/src/index.ts:563
    writeFileSync(this.pairingFile(), `${JSON.stringify(record, undefined, '\t')}\n`, 'utf8')
  • 读取本地文件
    packages/browser-unified-plugin/src/index.ts:652
    if (existsSync(local)) {
  • 读取本地文件
    packages/browser-unified-plugin/src/index.ts:927
    if (looksLikePath && !existsSync(exe)) {
  • 执行外部命令来自配置/变量
    packages/browser-unified-plugin/src/index.ts:932
    const child = spawn(exe, args, { detached: true, stdio: 'ignore' })
  • 发起网络请求
    packages/browser-unified-plugin/src/index.ts:1318
    const outcome = await approval.request({
  • 写入本地文件
    packages/browser-unified-plugin/src/index.ts:1471
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    packages/browser-unified-plugin/src/index.ts:1476
    await writeFile(file, buffer)
  • 写入本地文件
    packages/browser-unified-plugin/src/index.ts:1498
    await mkdir(path.dirname(file), { recursive: true })
  • 写入本地文件
    packages/browser-unified-plugin/src/index.ts:1500
    await mkdir(dir, { recursive: true })
  • 读取本地文件
    packages/browser-unified-plugin/src/index.ts:3088
    const pkg = JSON.parse(readFileSync(path.join(here, '..', 'package.json'), 'utf8')) as { version?: unknown }
  • 读取本地文件
    packages/browser-unified-plugin/src/index.ts:3100
    const doc = JSON.parse(readFileSync(file, 'utf8')) as { record?: { rows?: { title?: { val?: unknown } } } }
  • 读取本地文件
    packages/browser-unified-plugin/src/permissions.ts:133
    stat = statSync(file)
  • 读取本地文件
    packages/browser-unified-plugin/src/permissions.ts:143
    parsed = JSON.parse(readFileSync(file, 'utf8'))
  • 读取本地文件
    packages/browser-unified-plugin/src/unified-tools.ts:103
    const parsed: unknown = JSON.parse(await readFile(file, 'utf8'))
  • 执行外部命令固定命令
    packages/browser-unified-plugin/src/unified-tools.ts:111
    execFile('git', args as string[], { timeout: 25_000, maxBuffer: 1 << 20, windowsHide: true }, (error, stdout, stderr) => {
  • 发起网络请求
    packages/browser-unified-plugin/src/unified-tools.ts:229
    outcome = await approval.request({ agent, toolName, reason, signal: (exec as { signal?: AbortSignal }).signal })
  • 读取本地文件
    packages/browser-unified-plugin/src/unified-tools.ts:416
    const before = await readFile(registryFile, 'utf8')
  • 写入本地文件
    packages/browser-unified-plugin/src/unified-tools.ts:417
    await copyFile(registryFile, registryFile + '.bak')
  • 写入本地文件
    packages/browser-unified-plugin/src/unified-tools.ts:419
    await writeFile(registryFile, JSON.stringify(reg, null, 2) + '\n', 'utf8')
  • 写入本地文件
    packages/browser-unified-plugin/src/unified-tools.ts:547
    await copyFile(file, file + '.bak')
  • 写入本地文件
    packages/browser-unified-plugin/src/unified-tools.ts:548
    await writeFile(file, JSON.stringify(reg, null, 2) + '\n', 'utf8')
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

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

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

README

由 DeepSeek 最新模型翻译生成
dsh-browser-unified English · 中文 四条 DeepSeek Harness(dsh)浏览器插件路线合一的可安装插件:用你自己的真实浏览器 (DSH Browser Control 扩展 + 本地桥)给模型提供 27 个 browser_ 工具,前面统一加一层可配置的 URL 策略守卫,另附「上游自检 / 设计注册表」工具三件套与 GUI 设置分区。 仓库 = packages/(合并产物)+ upstream/(只留许可正文:四个第三方上游与前身项目各自的 LICENSE,源码基线由 upstream-baseline.json 的 commit SHA 钉住,可按 SHA 重新获取)。 合并核心与插件壳整包以 AGPL-3.0-only 发布(含 caob23/dsh-browser-control 桥收编代码,归属矩阵见 NOTICE.md)。 安装 一条命令产出全部 Release 资产(含自检闸门):node scripts/build-release.mjs → 产物在 dist-release/(三个包的 tgz + 扩展 zip + 兼容旧名的干净 zip + .sha256 + INSTALL.md)。
展开完整文档(README 剩余 1 节)
dsh-browser-unified

English · 中文

四条 DeepSeek Harness(dsh)浏览器插件路线合一的可安装插件:用你自己的真实浏览器 (DSH Browser Control 扩展 + 本地桥)给模型提供 27 个 browser_ 工具,前面统一加一层可配置的 URL 策略守卫,另附「上游自检 / 设计注册表」工具三件套与 GUI 设置分区。

仓库 = packages/(合并产物)+ upstream/(只留许可正文:四个第三方上游与前身项目各自的 LICENSE,源码基线由 upstream-baseline.json 的 commit SHA 钉住,可按 SHA 重新获取)。
合并核心与插件壳整包以 AGPL-3.0-only 发布(含 caob23/dsh-browser-control 桥收编代码,归属矩阵见 NOTICE.md)。

安装

一条命令产出全部 Release 资产(含自检闸门):node scripts/build-release.mjs → 产物在 dist-release/(三个包的 tgz + 扩展 zip + 兼容旧名的干净 zip + .sha256 + INSTALL.md)。

1. 浏览器扩展(必须用本仓库这版,不能用上游 caob23/dsh-browser-control 版):

从本仓库 Releases 下载 DSH-Browser-Control-v1.0.37.zip → 解压到任意目录 → edge://extensions(Chrome 为 chrome://extensions)开「开发者模式」→「加载解压缩的扩展」。
扩展源码就在本仓库 extension/,与插件同协议但多了 frames / 自建窗口 / 端口自动发现 / 导航完成判定 / div 化 SPA 元素识别等改动 —— 换回上游扩展这些能力会失效。
2. 插件(两个包一起装):从 Releases 下载 browser-unified-core-<ver>.tgz 与 dsh-browser-unified-<ver>.tgz,然后:

dsh plugin add github:Short-Arm-Ape/dsh-browser-unified 不能用。 实测(pnpm 12.5.1)会装出一个
@short-arm-ape/dsh-browser-unified@0.0.0 的占位包:仓库根目录不是包(真正的包在 packages/ 下),装出来的东西没有 lib/ 也没有 dsh.bundle.patch,dsh 不会挂载它。GitHub 上装的路径只适合"仓库根就是插件包"的仓库。这里请用下面的 Release 资产(file:),或者把仓库 clone 下来用 file: 指到 packages/ 里的目录。

推荐:由 CLI 落到 profile 的 vendor/ 并改好 package.json
dsh plugin --profile <profile> add file:<path>/browser-unified-core-<ver>.tgz \
file:<path>/dsh-browser-unified-<ver>.tgz

等价的手工写法:

pnpm add <path>/browser-unified-core-<ver>.tgz <path>/dsh-browser-unified-<ver>.tgz
package.json:
dependencies: "browser-unified-core": "file:<...>/browser-unified-core-<ver>.tgz"
"dsh-browser-unified": "file:<...>/dsh-browser-unified-<ver>.tgz"
dsh.profile.bundles: 加入 "dsh-browser-unified"

重启 dsh web 后生效(宿主新代码与 GUI client 半区都需要重启加载)。
包内 patch 已默认 enabled: true(装上即监听 9777);要关就在 profile 层按 id 覆盖 config。

想要 MIT 许可、不含 caob23/dsh-browser-control 派生代码的版本,用同仓库的 MIT 版 dsh-browser-unified-mit (不带浏览器桥与 browser_ 工具,仅 URL 策略 / 审批 / 自更新等模块)。

建议(专用配置文件):把 DSH Browser Control 扩展装在专用的浏览器配置文件里,与日常浏览隔离;需要访问本机服务(如 127.0.0.1)时把 urlMode 设为 intranet。跨 profile 自动拉起与 “无痕式临时干净配置”属拟议能力(design de-013),尚未实现。

配置与设置界面

GUI:设置 →「浏览器」(0.3.0+)

插件以 settings.section 在 DSH 设置对话框注册了独立的 「浏览器」 分区(形如 better-sidebar 的「侧边卡片」),逐项即时生效(改 URL 策略 / 云元数据列表只重建策略、不重启监听):

| 项 | 说明 |
| 启用浏览器桥 | 关闭则工具仍挂载,调用时报如何开启 |
| URL 策略档位 | public(默认,拦私网/回环/云元数据)/intranet(放行本地/LAN,仍拦云元数据) |
| 端口 / 令牌 / 截图目录 / registry 目录 | 监听与路径(改动重启本地桥) |
| 云元数据端点拦截 | 主开关 + 拦截主机名列表 + 拦截 IP 列表(整体替换内置默认;GUI 可「恢复内置默认」或「清空全部」) |

profile YAML(等价高级项,cordis.patch.yml)

- id: browser-bridge
config:
enabled: true
urlMode: public # public | intranet
blockMetadata: true # 云元数据拦截总开关
metadataHostnames: [metadata, metadata.google.internal, instance-data, instance-data.ec2.internal, metadata.azure.internal, metadata.tencentyun.com]
metadataIps: [169.254.169.254, 100.100.100.200, fd00:ec2::254]
registryDir: '<repo-dir>' # 指向本仓库根(含 design/registry.json + upstream-baseline.json);留空读包内 registry/ port: 9777, token: dsh-local, shotsDir: dsh-browser-shots(默认即可)

云元数据端点语义:内置默认(AWS/GCP/Azure 169.254.169.254、阿里 100.100.100.200、腾讯/阿里 metadata hostname 等)只是初始值;两条列表一旦给出即整体替换([] = 该族不拦),用于非三大云 / 自建 VPC 等场景的按需维护。public 档还有一条固定的默认主机黑名单(localhost / metadata 等)作为安全网,不受上述列表影响。

模型可用能力

| 工具 | 说明 |
| browser_navigate / read / snapshot / click / type / press / scroll / tabs / evaluate | 驱动真实浏览器(含登录态) |
| browser_screenshot / pdf / emulate | 截图(落盘 shotsDir)、导出 PDF、设备视口模拟 |
| browser_cleanup | 清理截图与 __ 前缀临时产物 |
| browser_console_log / network_log / network_clear | 页内 console / 网络请求观测 |
| browser_check_update | 只读:上游相对开发基线是否有新提交(DRIFT / UP-TO-DATE / UNREACHABLE) |
| browser_design_show / browser_design_edit | 读 / 受控改 design/registry.json(写必经审批,自动 .bak) |

导航类命令(nav / tabs.open)在发给扩展之前先过统一 UrlPolicy;被拦目标根本到不了浏览器。

安全模型

- URL 策略(src/url-policy.ts,MIT 上游语义蒸馏):public 档拦私网/回环/云元数据并做 resolve-then-validate DNS 复核;intranet 档有意放行本地/LAN,只保留元数据端点黑名单与用户黑名单。
- 鉴权:写入类操作(browser_design_edit、基线推进、源码变更)一律用户审批,fail-closed;外部内容(上游 commit / README / 网页文本)只作数据、绝不充当指令或工具参数。
- 已知边界(未实现里程碑):策略只拦“工具发起的导航”;页面自身 XHR / 重定向 / iframe / WebSocket / DNS-rebinding 需扩展端逐请求拦截(design de-005);browser_evaluate 尚无 allowEval 门控(de-006);无 GUI 实时画面(de-007)。设计注册表见 design/registry.json。

目录结构

dsh-browser-unified/
├── README.md 本文件 ├── NOTICE.md 上游来源/版本/许可证归属矩阵 ├── upstream-baseline.json 开发基线:五上游归档日 commit SHA ├── design/ 设计意图唯一书面出处 │ ├── README.md 工具契约 / 威胁模型与鉴权 / 条目状态机 │ └── registry.json impactRules(单一事实源)/ tooling / authz / designEntries ├── scripts/check-upstream.ps1 上游自检(只读) ├── .github/workflows/ci.yml CI:三包构建 + tests/ 全部套件(宿主侧,不碰真浏览器) ├── tests/ 回归套件 10 套(325 项断言)+ run-all.mjs 汇总入口 ├── upstream/ 只留各上游的 LICENSE(+ 上游自带的第三方声明);源码按 SHA 取 └── packages/ ├── browser-unified-core/ 合并核心(AGPL):url-policy / approval / bridge(收编) / guarded-bridge / self-update ├── browser-unified-plugin/ dsh 插件壳(AGPL):27 个 browser_* 工具 + 自更新三件套 + GUI「浏览器」设置分区 └── browser-unified-mit/ MIT 精简版(无桥、无驱动工具)

自动验证

两条线,别混在一起:

| 层 | 跑什么 | 什么时候 |
| CI(GitHub Actions,ubuntu + node 22) | 三个包 npm ci + 构建(走仓库自己的 build-pack)+ tests/ 全部 10 套(325 项断言)+ lib/client.js 与 client/client.js 字节一致 | 每次 push 到 main、每个 PR、手动触发 |
| 本地手动验证(真浏览器) | 只有它验得了的那层:浮层、硬锁、导航完成判定、div 化 SPA 元素识别 | 改扩展或改闸门时手跑(做法见 design/) |

本地一条命令:node tests/run-all.mjs(可带过滤词,例如 node tests/run-all.mjs tool)。
每个套件都是独立进程、自己数断言、自己用退出码交代结果,所以 CI 里不需要额外的断言解析。

CI 不做的事(免得看到绿就以为能上线):

- 不跑真浏览器:runner 上没有你的浏览器和已加载的扩展,「扩展装上去之后到底能不能点」它验不了;
- 不打 Release:node scripts/build-release.mjs 是本机发布流程(要真扩展 zip、要上传资产)。

CI 里有两处不那么直觉、但必须这样的地方(YAML 里也写了注释):

1. packages/browser-unified-plugin 用裸包名 import 'browser-unified-core',而 core 不是它的 package.json 依赖(本地也是手工装的)。npm 不管这条链接,CI 里用一条 ln -s 复原同一布局;
2. 构建走 node scripts/build-pack.mjs --no-pack 而不是裸 tsc:客户端那一半(client/client.js → lib/client.js)只有前者会复制,单跑 tsc 会让设置页测试读到旧文件。

上游与许可

| 归档 | 路线 | 浏览器 | 安全模型 | 并入 |
| dsh-browser-xylt369 | 类型化 seam + Playwright | 插件另起 Edge | 四层 URL 守卫 | ✅ url-policy(public)/approval |
| dsh-intranet-browser-short-arm-ape(本项目前身,非外部上游) | 上者内网分支 | 独立实例+profile | 元数据黑名单+逐次审批 | ✅ url-policy(intranet)/approval |
| dsh-browser-control-kyo615 | @playwright/mcp 子进程 | 另起 Chrome | 无 URL 校验 | ➖ 仅归档(live-view 候选) |
| dsh-browser-control-caob23 | 扩展+本地 WS 桥(CDP) | 用户真实浏览器 | 无 URL 校验(本插件已补) | ✅ bridge + GuardedBridge |
| dsh-agent-webops(参考上游) | headless Edge(CDP) 独立实例 | 一次性临时 profile | 无 URL 校验(参考) | ➖ 归档参考(de-019;de-013/17 相关) |

- 构建 / 验证:packages/browser-unified-core、browser-unified-plugin、browser-unified-mit 各自 npm run typecheck / npm run build;插件用 node scripts/build-pack.mjs(tsc→lib, client/client.js→lib/client.js,npm pack 到包根)。产物 dsh-browser-unified-0.7.3.tgz。
- 上游自检:pwsh scripts/check-upstream.ps1 [-UpstreamId <id>] [-UpdateBaseline]。
只查 role=merge 的条目(目前只有 caob23)—— reference 档只在决定接线某功能时翻归档取用,不做定时自检(2026-10-13 起)。检出 DRIFT 后必须先向你报告“上游改了什么、影响哪些合并模块”,你确认后 agent 才 re-vendor + 合并 + typecheck;-UpdateBaseline 只推进 merge 档的基线 SHA。改动会写 design/registry.json 条目。
- 安全红线:本机 git 缺 CA 时脚本内置 -c http.sslBackend=openssl -c http.sslVerify=false (只读参数);外部文本不得当指令执行。

与上游功能差距

(0.3.0 之后与四并入/候选上游逐项对比的差距清单:页面级请求拦截、allowEval 门控、GUI 实时画面、 stealth / web-permission 授权 UI 等未实现项,见 docs/gap-analysis.md,条目状态在 design/registry.json;dsh-agent-webops 为参考归档(非合并源),见 de-019。)详见对话产出的差距报告。

相关项目

- caob23/dsh-browser-control —— 本插件收编并扩展的上游:本地 WS 桥 + MV3 扩展,AGPL-3.0。
- dsh-intranet-browser —— 本插件的前身(已归档),URL 策略与审批层来自它。
- @deepseek-ai/dsh —— 宿主(DeepSeek Harness)。
- dsh-plugin.org —— 插件市场;它的安装失败报告里有本文档用到的诊断信息。

许可

- 合并核心与插件壳:AGPL-3.0-only(MIT 逻辑并入 AGPL 在法理上允许;caob23 源码行保留头部版权与来源,NOTICE.md 为完整归属矩阵)。分发 / 提供网络服务须遵守 AGPL-3.0 第 13 条。
- MIT 版:packages/browser-unified-mit。
预览分享卡片长什么样 ↓Short-Arm-Ape/dsh-browser-unified
分享 · DeepSeek Harness Hub
Short-Arm-Ape/dsh-browser-unified

DeepSeek Harness(dsh)浏览器插件

回到顶部