# dsh-mobile-access > 受 PIN 保护的、仅对话的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)手机网页端,在独立端口上通过局域网或自备隧道访问——全程不暴露桌面网页界面。 [English](./README.md) · [中文](./README.zh-CN.md) [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![node: >=20](https://img.shields.io/badge/node-%3E%3D20-green.svg)](./package.json) [![dsh-plugin](https://img.shields.io/badge/dsh-plugin-remote-8b5cf6.svg)](https://github.com/topics/dsh-plugin) --- ## 它能做什么 DSH 的网页界面绑定在 `127.0.0.1` 是有意为之——它暴露了文件、终端和设置,一旦对外开放就等于把这一切都开放出去。本插件不动那个桌面界面,而是另起一个**刻意收窄**的 HTTP 监听,只提供**对话**能力:查看会话、继续聊天、查看和桌面一致的统计。同一 Wi-Fi 下用手机直连,外出时走隧道。 ## 特性 ### 手机端(`/m/`) - **PIN 登录** → 按工作区分组的会话列表 → 实时对话。 - **流式回复**,内联展示工具调用步骤。 - **按会话切换模型与权限预设**,无需离开聊天页。 - **与桌面一致的会话统计**——轮数、步骤、LLM 耗时、token、费用。 - **轮次导航栏**——跳转到任意一轮,支持悬浮预览与自动加载更早历史。 - **分页加载**(加载更早)、**明暗主题**(默认亮色)、**消息提示(toast)**。 ### 桌面设置页 桌面 UI 里的一张标准 DSH 设置卡: - 总开关、端口、网络模式(`lan` / `tunnel` / `both`)、隧道主机名、空闲超时。 - 设置/重置 PIN(可显隐)、扫码连接手机、吊销会话、查看登录历史、隧道教程(Tailscale / cloudflared / cpolar)。 ### 安全 - **默认关闭**;未设置 PIN 前只监听本机,配置完成前无法从外部访问。 - **PIN**:8 位数字,拒绝弱 PIN,以 scrypt(`N=2^14, r=8, p=1`)+ 随机盐存储,常量时间比较。 - **令牌**:256 位随机值;仅持久化 `sha256(token)`;7 天过期 + 空闲超时(默认 60 分钟)。 - **防爆破**:每 IP 每分钟 6 次登录尝试;失败 5 次锁 15 分钟,重复触发翻倍(持久化)。 - 独立监听器上的 **Host 白名单**;所有写请求带 **CSRF 防护**。 - 桌面网页界面保持仅回环——无需 `--host 0.0.0.0`,不改动 DSH 自身的信任边界。 ## 架构 ``` ┌─────────────────────────── DSH 主机(仅回环)──────────────────────────────────┐ │ 桌面网页界面 (127.0.0.1:3080) ← 保持不变,永不对外 │ │ dsh-mobile-access 宿主插件 │ │ settings · sessionQuery · agents · apiProxy · permissionPresets · ... │ └──────────────────────────────────────┬─────────────────────────────────────────┘ │ 进程内服务调用 ┌──────────────────────────────────────▼─────────────────────────────────────────┐ │ 独立 HTTP 监听(LAN 用 0.0.0.0:3081,隧道用 127.0.0.1) │ │ PIN 认证 · Host 白名单 · CSRF · SSE │ │ /m/ 手机 SPA(preact,静态托管) │ │ /m/api/* 对话 / 会话 / 统计 / 模型 / 权限 │ │ /m/api/admin/* 仅回环的管理接口(PIN、会话、登录历史) │ └────────────────────────────────────────────────────────────────────────────────┘ ▲ 局域网(HTTP)或隧道(Tailscale / cloudflared / cpolar,HTTPS) │ 📱 手机浏览器 ``` 插件在进程内桥接到 DSH 自身的宿主服务(`sessionQuery`、`agents`、`apiProxy`、`permissionPresets`、`sessionProjections` 等)。它不是重写 DSH,而是把其中一小块经过筛选的能力暴露出来。 ## 环境要求 - DeepSeek Harness `0.1.1-rc.2`(`@deepseek-ai/*` 包均声明为 `peerDependencies`)。 - Node.js ≥ 20。 ## 安装 仓库自带预构建的 `lib/` 与 `dist-webapp/`,因此从 Git 安装无需构建步骤: ```sh dsh plugin --profile web add github:huanlanmie/dsh-mobile-access ``` 重启 `dsh web`。桌面设置里会出现「手机访问」卡片——用它设置 PIN、启用服务并扫码连接。设置 PIN 前服务仅回环。 > 本地开发安装:`dsh plugin --profile web add link:`(或从 checkout 的上一级目录用 `./dsh-mobile-access`)。 ## 快速上手 1. 在本机打开桌面网页界面的「手机访问」设置卡,设置 8 位 PIN。 2. 启用并选择模式: - **`lan`**——监听 `0.0.0.0`;手机在同一 Wi-Fi 下扫码访问。 - **`tunnel`**——填入你的隧道主机名(见下),走 HTTPS 访问。 - **`both`**——在家用 LAN,外出用隧道。 3. 手机打开 `/m/`,输入 PIN,继续你的会话。 ### 隧道 | 提供商 | 隧道主机名填什么 | 说明 | | --- | --- | --- | | Tailscale | `mypc.tail12345.ts.net` | tailnet 内已加密,推荐。 | | cloudflared | 临时 `*.trycloudflare.com` 主机名 | Cloudflare 终止 HTTPS 后本地转发。 | | cpolar | 分配的 `*.cpolar.top` / `*.cpolar.io` 主机名 | HTTPS 隧道;token 保存在 `.cpolar/`。 | ## 配置项 | 键 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `enabled` | boolean | `false` | 总开关。关闭 = 完全不监听。 | | `port` | number | `3081` | 插件独立 HTTP 面的端口(1024–65535)。 | | `mode` | `lan` \| `tunnel` \| `both` | `lan` | 允许哪些网络访问该面。 | | `tunnelHost` | string | `""` | 走 HTTPS 的隧道主机名。 | | `idleTimeoutMin` | number | `60` | 会话空闲超时(分钟,5–10080)。 | 这些是 `mobile-access` 命名空间下的 DSH 设置,可在桌面设置卡里直接修改。 ## 安全说明 - **LAN 模式是明文 HTTP。** PIN 与令牌可能被同一网络上的攻击者嗅探。仅在可信网络下使用,其余场景请走隧道。 - 设置或重置 PIN 会吊销全部会话。 - 这是第三方代码,不属于 DSH 本体。在承载你会话的机器上安装前,请先审阅源码。 ## 开发 ```sh npm install npm run build # esbuild 打包手机 SPA + 桌面客户端,再 tsc 编译宿主 npm test # node:test —— PIN 格式、令牌哈希、锁定、Host 白名单 npm run typecheck # tsc --noEmit ``` 目录结构: ``` src/ 宿主插件(TypeScript):auth、server、settings、storage、 net(白名单/URL)、mux(提问/审批桥接)、API 路由 src/client/ 桌面设置卡(React) webapp/src/ 手机 SPA(preact):main.tsx、api.ts、styles.css scripts/ build-webapp.mjs(esbuild + 客户端 bundle 封装) test/ node:test 测试套件 ``` 预构建的 `lib/` 与 `dist-webapp/` 已提交到仓库,Git 安装无需构建步骤;`npm run build` 会重新生成它们。 ## 截图 ## 许可 [MIT](./LICENSE)