DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

ImHaoYuan /

ImHaoYuan/dsh-mobile-mirror

Verified

DSH 插件:一键搭建局域网服务器,手机浏览器直连,也可下载调试版 App

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: master@6ba33c32

dsh-mobile-mirror

在局域网里用手机镜像 DSH(DeepSeek Harness)的会话:看会话列表(按工作区分组)、看历史、 看实时逐字输出,以及发消息、停止当前轮、新建会话、切换会话的模型与模式、 在手机上回答 DSH 的提问。

桌面端行为完全不变 —— 不注入 UI、不遮挡、不改布局。手机页面走自己的端口, DSH 现有 webServer 的 host / port / 既有行为一律不动(只额外挂一条只读、仅回环的 JSON 路由给桌面设置面板取地址)。

当前版本 1.2。网页侧功能随主机更新,无需重装 APK(APK 仍是 1.1 / versionCode 8)。 仅测试运行DSH Windows桌面端0.2.0-rc.2与HyperOS3


特性

手机端(https://<电脑局域网IP>:19388/)

能力 说明
会话列表 按工作区分组、可折叠(折叠状态记在 localStorage)、子智能体会话紧跟父会话
实时输出 SSE 逐字推送;切后台回来自动补齐,不丢不重
发消息 幂等(requestId)+ 每会话 300ms 节流;弱网重发不会重复发出
停止当前轮 页面内二次确认:点一下按钮变成「确认停止?」,3 秒内再点才真停。不用原生弹窗 —— APK 的 WebView 没接管 onJsConfirm,window.confirm 在那里恒为 false,点了等于没点
新建会话 从工作区清单里选,或手输任意绝对路径(一个会话都没有的新文件夹也能开)
模型 / 模式 切换会话的模型(含推理档位)与模式(标准 / PTC / 极简 / 创造 + 自建)
回答提问 手机与桌面谁先答谁生效;桌面 GUI 没开时手机照样能答。tool-ask-user 若配成 mode: timed(限时提问),手机看着卡片期间会接管等待:宿主不再自己计时,手机上从容作答也不会掉进"迟到回复"(答案被暂存、agent 又跑一轮);切后台 / 离开聊天页 / 关掉页面立刻放开,不会把 agent 卡住。默认的 legacy 模式没有限时等待,认领自然不生效
快捷跳转 右侧刻度条只标我自己说过的话:说过两句以上才出现,点一下跳过去、按住先看那句原话、滚动时高亮当前那句(长会话里回头找自己说的话)
收起提问卡片 卡片紧贴输入框上方,不收起来会一直挤占会话消息;收起后只剩标题 + 还剩几题,换新问题自动展开
工作过程折叠 一轮里的思考与命令(read / write / edit 这些工具调用)收进同一张「工作过程」折叠卡:正在跑时展开、回答结束自动折起,标题上留着件数;收起来时右侧写着「点开看」。卡片在第一件工作到来时现场建,所以历史快照里同样有(快照窗口切掉了某一轮的 turn/start 也不影响)。块全进了卡、自己没有正文的消息整条不再渲染 —— 以前它们会退化成一句「(无内容)」,一轮下来满屏都是它
长内容不截断 正文 / 思考上限 100000 字符、工具参数 20000 字符、请求体 1MB —— 长回答与长粘贴不再被截掉
Markdown 标题 / 列表 / 表格 / 任务列表 / 引用 / 代码块(带语言名与复制键),手写实现
主题 深浅两套跟随系统;内嵌 JetBrains Mono(只用于代码)
干净的消息流 AGENTS.md、运行时上下文、技能目录等注入内容在宿主侧就被丢掉,手机上只剩真人说的话

桌面设置面板

DSH 设置里多一页「手机镜像」:手机访问地址(一键复制、按连通性排序、其他网卡可展开)、 本机设置页直达链接,以及协议 / 端口 / 账号 / 口令状态 / 证书指纹 / 有效期 / 在线会话数。 打开期间每 10 秒自动刷新,换 Wi-Fi 导致 IP 变了会自己跟上。

APK

android/ 里是一个纯 Java、零第三方依赖的 WebView 外壳,把手机页面装进 App 并做三件 网页做不到的事:

  1. 证书指纹固定(TOFU) —— 不用装系统 CA,信任只限本 App;
  2. 通知 —— 前台服务轮询两个只读端点,DSH 提问或一轮跑完时手机锁屏也能知道;
  3. 小米超级岛 —— 有会话在跑就上岛、岛上显示会话标题、出现提问时变色。(仅测试HyperOS3)

详见 docs/apk-plan.md。


为什么是独立端口 / 为什么有 HTTPS

DSH 自己的 Web 服务只监听 127.0.0.1。把它的 host 改成 0.0.0.0 虽然一行配置就能让 手机打开完整 GUI,但那等于把整个 GUI 和全部 /api 暴露到局域网。

本插件换一条路:自己起一个只服务手机页面的小 HTTPS 服务(默认 0.0.0.0:19388), 只放行手机页面需要的接口,自带独立凭据。桌面 GUI 与 /api 的暴露面保持不变, 关掉插件就彻底消失。

局域网明文 HTTP 下,同一个 Wi-Fi 里任何能抓包的人都能拿到你的密码,所以默认走 TLS。 证书是现场签发的自签证书,不依赖 openssl、不引第三方包 —— lib/asn1.js + lib/cert.js 手写 DER 组装 X.509,node:crypto 生成密钥与签名。


环境要求

项 要求
DSH 桌面端0.2.0-rc.2。插件接口用 dsh.bundle.patch + dsh.client(桌面设置面板需要后者)
Node.js >= 20
npm 依赖 无。宿主侧、页面、测试全部零第三方依赖
手机 与电脑在同一局域网;iOS Safari / Android Chrome 均可
构建 APK(可选) JDK 17 或 21、Android SDK Platform 36 + Build-Tools 36

安装

本插件是 DSH bundle 插件,通过 profile 的 package.json 注册。 把下面所有 <路径> 换成你克隆本仓库的实际绝对路径。

1. 克隆

git clone https://github.com/ImHaoYuan/dsh-mobile-mirror.git

2A. 手动 link

编辑 DSH profile 的 package.json(默认 ~/.dsh/profiles/desktop/package.json):

{
  "dependencies": {
    "dsh-mobile-mirror": "link:<你克隆到的绝对路径>"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "...",
        "dsh-mobile-mirror"
      ]
    }
  }
}

然后在 profile 目录里建好链接(npm install / pnpm install),重启 DSH。

Windows 上路径写成正斜杠或双反斜杠,例如 link:D:/plugins/dsh-mobile-mirror。

2B. 用 DSH 的插件管理器

plugin_manager install_bundle  target = link:<你克隆到的绝对路径>

它会自动改好 package.json、建好 node_modules 链接,并热应用到运行中的 profile。

装好了怎么确认

启动日志里会出现 listening on 0.0.0.0:19388;或者打开 DSH 的设置,能看到「手机镜像」那一页。


首次使用:设置账号密码

  1. 在电脑上打开 https://127.0.0.1:19388/setup (自签证书,浏览器会先警告一次,点"继续访问"即可)
  2. 填账号和密码(至少 6 位),保存。

没设置之前任何人都登录不了(fail closed),不存在"没配密码所以谁都能进"。

也可以直接编辑 $DSH_HOME/mobile-mirror.json 填明文 password, 下次启动会自动转成 scrypt 哈希并把明文从文件里删掉。


手机访问

  1. 手机连同一个 Wi-Fi
  2. 打开 https://<电脑局域网IP>:19388/ (地址看桌面 设置 → 手机镜像 那一页,或 /setup 页面的"手机访问地址")
  3. 首次会看到证书警告 —— 自签证书的正常表现:
    • iOS Safari:「显示详细信息」→「访问此网站」
    • Android Chrome:「高级」→「继续前往」
  4. 用刚设的账号密码登录

想消掉警告:在电脑上 /setup 页面下载 cert.pem,装到手机上:

  • iOS:安装描述文件(设置 → 通用 → VPN与设备管理),再到「关于本机 → 证书信任设置」打开完全信任
  • Android:设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书

不装也能用,只是每次会先看到一个警告页。

加到主屏幕:页面已带 apple-mobile-web-app-capable,iOS 上"添加到主屏幕"后是全屏、无地址栏。


桌面设置面板

DSH 界面左下角头像 → 设置,里面会多一页「手机镜像」(排在「智能体预设」之后):

  • 手机访问地址 —— 当前该用哪个 https://<IP>:19388/,按最可能连得上排序,一键复制; 其他网卡(VMware、虚拟网卡、169.254 之类)折在「其他网卡」里。
  • 本机设置页(回环) —— https://127.0.0.1:19388/setup 的直达链接。
  • 状态 —— 协议、端口、账号、口令是否已配置、证书指纹、有效期、在线会话数。

数据来自主机侧在 DSH GUI 服务上注册的只读、仅回环路由 GET /dsh-mirror/info.json; 拿不到 webServer 时只是面板显示「读不到主机信息」,手机镜像本身完全不受影响。 细节与理由见 docs/design.md。

改 lib/client.js(浏览器 bundle)与主机侧路由都需要重启 DSH。


APK(可选)

只有想用通知 / 超级岛才需要。本仓库不提交构建产物,需要自行构建; 在 Releases 页面附了预编译 APK,也可以直接下载安装。

# JDK 用 17 或 21(AGP 8.5.2 不支持过新的 JDK,例如 26 会直接失败)
$env:JAVA_HOME = 'C:\Program Files\Zulu\zulu-21'
cd <仓库路径>\android
.\gradlew.bat assembleDebug        # macOS / Linux 用 ./gradlew assembleDebug

Android SDK 的定位方式二选一:设 ANDROID_HOME 环境变量,或写 android/local.properties (该文件已 gitignore,各人不同):

sdk.dir=D\:\\AndroidStudio\\SDK

产物在 android/app/build/outputs/apk/debug/app-debug.apk,自己传到手机安装 (手机需允许「安装未知来源应用」)。交付包请从干净构建取(gradlew clean assembleDebug): 增量构建会在 ZIP 里留几十 KB 页对齐填充,体积虚高。

首次打开 App 要填电脑的 https://<IP>:19388 与证书指纹(设置页里能看到), 之后走 TOFU 指纹固定。超级岛还需要在 HyperOS 设置里为本 App 打开「焦点通知」。


配置

$DSH_HOME/mobile-mirror.json(首次启动自动生成,每次启动会归一化重写):

字段 默认 说明
port 19388 监听端口
username dsh 登录账号
passwordHash null scrypt 加盐哈希,由 /setup 写入
password — 只用于手写明文,加载后转哈希并删除
sessionTtlDays 30 登录会话在服务端的有效期。Cookie 不带 Max-Age,关掉浏览器就失效,所以这个值只约束"标签页一直开着"的情形
tls true 关掉会退回明文 HTTP(不推荐)
certDir null 证书目录,默认 $DSH_HOME/mobile-mirror-cert
allowedHosts [] 额外的 Host 白名单(一般不需要)
enablePrompt true 写操作总开关。设 false 即只读模式:发消息、停止轮次、切换模型、切换模式、回答问题全部返回 403

安全边界(摘要)

  • 只在局域网:不做任何内网穿透。手机在外网时用不了 —— 这是刻意的。
  • Host 校验:只接受回环、私有网段 IPv4(10 / 172.16–31 / 192.168 / 169.254)或显式白名单,挡 DNS rebinding;回环判定看 socket 真实来源地址,不看 Host 头。
  • 跨站一律拒:Sec-Fetch-Site: cross-site 直接 403;带 Origin 时必须与 Host 同源。
  • 口令:scrypt 加盐哈希 + timingSafeEqual;连续失败指数退避,10 次后锁 5 分钟(锁定期连 scrypt 都不跑)。
  • 会话:HttpOnly + SameSite=Strict + Secure 的随机 Cookie,不带 Max-Age;改密码会注销所有旧会话。
  • 写操作三道防线:SameSite=Strict Cookie、Origin 同源、强制 Content-Type: application/json。
  • 静态资源白名单:路径必须命中固定表,不做路径拼接,目录遍历天然不成立。

完整清单(含每条路由的认证要求)见 docs/design.md。


开发与测试

npm test     # 一次跑完八套,共 1777 项
命令 覆盖 项数
node tools/cert-test.mjs 证书层(含真实 TLS 握手) 27
node tools/smoke.mjs HTTPS + 认证集成 32
node tools/mirror-test.mjs 数据层 + 全部路由(含 P3 / P4 / P5 / 提问认领) 521
node tools/host-test.mjs 入口层:真跑一遍 apply(),含桌面面板路由 83
node tools/web-test.mjs 页面静态资源断言 + 纯函数 + 接线 + 客户端 bundle 433
node tools/client-test.mjs 桌面设置面板:bundle 格式、槽注册、字段名耦合 61
node tools/web-pure-test.cjs app.js 导出的纯函数(重点是 Markdown) 189
node tools/web-dom-test.cjs 用 fake DOM 真跑一遍页面行为 431

八套都不需要启动 DSH,使用临时目录里的证书与配置,不碰 $DSH_HOME。 每套覆盖什么、抓到过什么 bug,见 docs/design.md。

改代码后要不要重启 DSH:

改哪里 生效方式
lib/web/*.html / .js / .css 即时生效(每次请求现读磁盘),刷新手机页面即可
lib/*.js(宿主侧)、lib/client.js、路由 必须重启 DSH

宿主插件模块按 URL 缓存,hmr 只暴露 watchConfig / getLinked,没有模块失效接口 —— 所以"禁用→启用"拿到的仍是旧代码。这是平台限制,不是插件问题。


常见问题

手机打不开页面 / 一直转圈 先确认手机和电脑在同一个 Wi-Fi(不是访客网络、不是手机热点),再确认地址里的 IP 是电脑的 局域网 IP(桌面设置面板里那一串)。电脑防火墙可能拦了 19388 —— 放行入站 TCP 19388 即可。

浏览器说"不是私密连接" 自签证书的正常表现。点"继续访问",或按上面的步骤把 cert.pem 装到手机上消掉警告。

忘了密码 在电脑上重新打开 https://127.0.0.1:19388/setup 重设(回环访问不需要登录)。 也可以在 mobile-mirror.json 里写明文 password 后重启 DSH。

模式芯片是灰的 DSH 的规则:会话一旦跑过至少一轮,模式就锁死了(preset-locked)。新建的空白会话才能换。

端口 19388 被占用 启动日志会写 监听 0.0.0.0:19388 失败。改 mobile-mirror.json 的 port,重启 DSH。

超级岛不出现 先在 HyperOS 设置里为本 App 打开「焦点通知」(超级岛不是申请白名单,而是运行时按包名裁定); 另外「转圈」做不到 —— 环只来自确定值进度弧,颜色才承担状态语义。

手机上少了几条消息 AGENTS.md、运行时上下文、技能目录、审批通知这些注入内容被故意丢掉了,只保留真人说的话。 判定用白名单(只认 source.kind === 'user'),取不到来源时按真人消息处理 —— 宁可多显示,不会吞掉你说的话。

手机上答完,电脑上的提问卡片还挂着 tool-ask-user 默认是 mode: legacy(没有超时),而 DSH 的提问投影 (userQuestions)只登记限时提问,桌面卡片的移除又遵循投影 —— 于是 legacy 下 手机作答不会结算桌面那张卡,它就一直挂在那儿;你如果又在桌面答一次,那一次会被 gateway 静默丢弃("另一个浏览器先结算了同一请求")。想让桌面卡片随手机作答一起收掉, 在 cordis.patch.yml 里给它加一段配置:

- id: tool-ask-user
  name: '@deepseek-ai/dsh-tool-ask-user'
  config:
    mode: timed      # 让投影开始登记提问(schema 声明 timeout ⇒ 投影认为它限时)
    timeout: -1      # 但仍然不设 deadline:工具走非限时那条路,不会出现"迟到回复"

timeout: -1 与 mode: timed 的组合是关键:投影登记了提问(桌面卡片能被结算), 而工具仍然等下去(不会到点放行模型、不会把答案变成"暂存 + 又跑一轮")。 若改成正数(如默认 120),就真的会有 deadline —— 那时手机看着卡片会接管等待 (宿主不再计时),但桌面自己那张卡片的本地倒计时仍会到点放行,这是 DSH 的设计。


目录结构

dsh-mobile-mirror/
├── lib/                    宿主侧(Node)
│   ├── index.js            入口:apply()、配置加载、服务启停、桌面面板路由
│   ├── server.js           HTTP(S) 服务、路由、认证、静态资源
│   ├── mirror.js           会话数据 → 镜像协议(事件投影、SSE、注入内容过滤)
│   ├── auth.js             scrypt 口令哈希与会话存储
│   ├── cert.js             自签证书签发与复用
│   ├── asn1.js             手写 DER 组装(不引第三方包)
│   ├── client.js           桌面设置面板的浏览器 bundle(手写,无构建步骤)
│   └── web/                手机页面:index / login / setup + app.js / app.css + 内嵌字体
├── tools/                  八套自测(不需要启动 DSH)
├── android/                WebView 外壳 APK(纯 Java,零第三方依赖)
├── docs/                   design.md(设计说明)、apk-plan.md(APK 方案与施工记录)
├── cordis.patch.yml        DSH bundle patch
├── package.json
├── THIRD_PARTY_NOTICES.md  第三方声明(字体、协议事实出处)
└── LICENSE

许可证与致谢

MIT © 2026 ImHaoYuan

  • 内嵌字体 JetBrains Mono(Regular + Bold),SIL Open Font License 1.1, 许可证全文见 lib/web/fonts/OFL.txt。
  • 小米超级岛(焦点通知)的 extras 键名与 JSON 结构,取自开源项目 ABK(GPL-3.0)的实现记录 —— 这些是 HyperOS 的接口事实(写错系统就静默丢弃),不是它的代码。本项目为独立编写的 Java 实现, 未使用其任何代码;完整声明见 THIRD_PARTY_NOTICES.md。
  • 界面借用了 DeepSeek 网页端的组件语言(助手消息不带气泡、代码块头部条等), 未使用其商标、图标或文案。

更多文档

  • docs/design.md —— 镜像协议、事件投影、安全边界、每一项取舍的理由
  • docs/apk-plan.md —— APK 方案选型、超级岛字段与施工记录
—/ 5

No ratings yet

Verified DSH bundle

Commit 6ba33c328658

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout