# dsh-pip

<p align="center"><strong>一键把 DeepSeek Harness 的 Web 界面拽进一个始终置顶的画中画窗口——写代码的时候它悬浮在旁边，点一下就回去。</strong></p>

<p align="center">
  <a href="https://www.npmjs.com/package/dsh-pip"><img src="https://img.shields.io/npm/v/dsh-pip?style=flat-square" alt="npm" /></a>
  <a href="https://github.com/extension-hunter/dsh-pip/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
  <img src="https://img.shields.io/badge/browser-Chromium%20116%2B-4285F4?style=flat-square&logo=googlechrome&logoColor=white" alt="Chromium 116+" />
  <img src="https://img.shields.io/badge/config-%E9%9B%B6%E9%85%8D%E7%BD%AE-8A2BE2?style=flat-square" alt="零配置" />
</p>

<p align="center"><a href="README.md">English</a> · 中文</p>

## 为什么做这个

DSH 的 Web 界面活在一个浏览器标签页里——想同时写代码又盯着 Agent，就得在编辑器和标签页之间来回切窗口。`dsh-pip` 用 [Document Picture-in-Picture](https://developer.chrome.com/docs/web-platform/document-picture-in-picture) 把**整个应用**（不是截图）搬进一个系统级置顶窗口：

- **一条命令安装。** 包自带组合补丁（`dsh.bundle.patch`）：`dsh plugin add` 自动完成插件行挂载，不用手改任何文件。
- **零配置。** 装完重启 `dsh web`，界面右下角多一个「独立窗口」悬浮按钮，点它就完事。
- **窗口默认和浏览器一样大。** 独立窗口按当前标签页视口尺寸打开；想固定大小，两个数字就能覆盖。
- **整个应用都在。** 按钮点击后，`#root` 连同样式、主题一起被搬进新窗口——不是投屏、不是截图，是应用本体，交互照常。
- **不支持的浏览器零打扰。** Safari / Firefox 没有 Document PiP API，脚本直接 no-op，连按钮都不会出现。

<p align="center">
  <img src="assets/dsh-pip-overview.png" width="100%" alt="dsh-pip 把实时 Harness Web UI 从浏览器标签页搬进编辑器旁的始终置顶窗口，并可一键移回。" />
</p>

## dsh-pip 与 DeepSeek Harness Desktop 怎么选

这两个项目解决的是同一工作流里的不同层级。`dsh-pip` 面向已经在使用 `dsh web`、只想让界面常驻编辑器旁边的用户；[DeepSeek Harness Desktop](https://github.com/anywhere-labs/deepseek-harness-desktop) 则是完整的 Electron 桌面发行版，同时接管桌面应用和本地 Harness 服务的生命周期。

| | `dsh-pip` | DeepSeek Harness Desktop |
|---|---|---|
| 核心定位 | 把现有 Web UI 分离成始终置顶的伴随窗口 | 把 Harness 打包成独立桌面应用 |
| 安装方式 | 一条 `dsh plugin` 命令 | 下载并安装桌面端构建 |
| 现有环境 | 需要可用的 `dsh web` profile 和 Chromium 116+ | 自带运行环境，不用手动配置 Node.js 或从命令行启动 |
| Harness 服务 | 复用现有服务和配置，不负责启动或管理 | 自动启动并管理本地 Harness 服务 |
| 窗口体验 | 一键分离 / 返回的 Document PiP 窗口，始终悬浮在编辑器上方 | Electron 桌面窗口、系统托盘和桌面界面适配 |
| 官方界面与插件 | 原样搬动当前官方 Web UI，并通过 DSH 插件机制安装 | 在桌面发行版内保留官方本地 Web UI 和插件能力 |
| 远程访问与 Channels | 不提供，只增强本机窗口体验 | 仓库宣称支持 iOS / Android 手机远控，以及微信、飞书、Discord、WhatsApp Channels |
| 插件发现 | 使用标准 DSH 插件命令，不提供市场 | 插件市场以及把 Desktop 作为 DSH 插件交付仍在规划中，尚未发布 |
| 平台范围 | 支持 Document PiP 的 Chrome / Edge | macOS、Windows 桌面构建 |

**如果你已经把 Harness 跑起来了，只想用最小改动获得悬浮窗口，选 `dsh-pip`**：原来的运行方式、浏览器、profile 和升级链路都不变。**如果你想要免配 Node.js、无需命令行启动，并由桌面应用统一管理 Harness，选 Desktop。**

## 快速开始

```sh
dsh plugin --profile web add dsh-pip
```

重启 `dsh web`——完成，零配置：

- 插件的 bundle 补丁自动挂载插件行并把浏览器脚本注入每个页面——纯增量、不碰核心行；
- 打开 Web 界面，右下角出现「独立窗口」按钮，点击即分离；
- 关闭独立窗口（或点窗口里的「返回标签页」），应用回到原标签页，按钮恢复。

## 工作原理

点击按钮时，浏览器脚本在点击的瞬时激活期内调用 `documentPictureInPicture.requestWindow()`，把原页面的样式表和主题属性复制进新窗口，把应用根节点（`#root`）整体迁入，并隐藏原页面按钮。窗口的 `pagehide` 事件——由浏览器自带的「回到标签页」控件或脚本自己的「返回标签页」按钮触发——把根节点搬回去、恢复按钮。

| 一半 | 位置 | 职责 |
|---|---|---|
| 宿主插件 | `src/index.ts` | 注册 `/pip/client.js` 路由，把浏览器脚本 + 配置注入每个 index 响应 |
| 浏览器脚本 | `src/client.js` | 画悬浮按钮，把应用（`#root`）搬进 / 搬出画中画窗口 |

## 配置项

全部可选，默认即可用。在 profile 的 `~/.dsh/profiles/web/cordis.patch.yml` 里覆盖：

| 字段 | 默认值 | 含义 |
|---|---|---|
| `enabled` | `true` | 挂载插件；`false`（或行上 `disabled: true`）关闭 |
| `width` | 省略 | 独立窗口宽度（CSS px）；省略 = 跟随浏览器视口宽度 |
| `height` | 省略 | 独立窗口高度（CSS px）；省略 = 跟随浏览器视口高度 |

```yaml
- id: pip
  config:
    enabled: true
    width: 1280   # 想固定尺寸就写数字
    height: 800
```

> `requestWindow` 的宽高是建议值，Chromium 会把窗口限制在屏幕可用范围内，最大化浏览器时实际尺寸可能略小。

## 环境要求

- **Chromium 116+**（Chrome、Edge）。Safari / Firefox 不支持 Document Picture-in-Picture API，脚本 no-op，不出现按钮。
- `webServer` 服务——`web` profile 已通过 `@deepseek-ai/dsh-host-webserver` bundle 提供。

## 安装与生命周期

### 安装

```sh
dsh plugin --profile web add dsh-pip
```

开发时装未发布的本地检出：

```sh
dsh plugin --profile web add file:/path/to/dsh-pip
```

### 禁用 / 恢复

```yaml
- id: pip
  disabled: true
```

改回 `false` 即恢复。

### 升级

```sh
dsh plugin --profile web update dsh-pip
```

### 卸载

```sh
dsh plugin --profile web remove dsh-pip
```

同时移除依赖与 bundle 层，不残留任何文件。

## 已知限制

- **仅 Chromium** —— Safari / Firefox 永远不会出现按钮。
- **Portal 浮层留在标签页** —— 应用通过 `createPortal(..., document.body)` 渲染菜单、弹窗、toast 和悬浮卡；分离期间这些元素仍指向原标签页的 body，要等应用回去才可见。独立窗口适合阅读 / 稳态浏览，不适合在里面开菜单。
- **window 级监听不跟走** —— 全局 `window`/`document` 监听器（快捷键、滚动跟踪）仍在原标签页触发；只有迁移根节点上的 React 管理事件跟随窗口。

## 开发

```sh
npm install
npm run typecheck   # tsc --noEmit
npm run build       # tsc → lib/ + copy src/client.js → lib/client.js
```

`npm publish` 前的 `prepack` 会自动重新构建，发布的 tarball 始终携带最新的 `lib/`。

## License

[MIT](LICENSE)
