# deepseek-harness-desktop

> 在 DSH Web 聊天会话顶部加一个智能体模式切换下拉，空白会话原地切换、有历史的会话则在同一工作区先开新会话再切，原有对话不会被打断。

## Metadata

- Author: [@ningbainb](https://github.com/ningbainb)
- Repo: <https://github.com/ningbainb/deepseek-harness-desktop.git>
- GitHub: [ningbainb/deepseek-harness-desktop](https://github.com/ningbainb/deepseek-harness-desktop)
- Stars: 156
- Language: TypeScript
- License: [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html)
- Homepage: <https://ningbainb.github.io/deepseek-harness-desktop/>
- Topics: `ai-agent`, `ai-coding-assistant`, `codex`, `deepseek`, `deepseek-harness`, `desktop-app`, `dsh`, `dsh-plugin`, `electron`, `electron-app`, `gui`, `open-source`, `plugin-system`, `plugins`, `remote-access`, `skills`, `ssh-client`, `windows`, `windows-desktop`
- Forks: 5
- Open Issues: 6
- Last push: 2026-08-20T05:29:52.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:ningbainb/deepseek-harness-desktop/packages/dsh-mode-switcher
```

## Wiki

## 一句话定位
在 DSH Web 聊天会话顶部加一个智能体模式切换下拉框；切换空白会话时原地生效，遇到已有对话的会话则先在同一工作区创建新会话再应用新模式，避免破坏当前聊天上下文。

## 核心能力
- 顶部下拉切换：在会话头注入一个胶囊样式 `<select>`，列出 DSH 运行时提供的所有可用智能体模式（Plan / Code / 等），选中即调用 `api.agentPresets.select` 把当前会话的模式替换为目标预设
- 自动过滤坏掉的预设：列表加载时跳过 `broken !== undefined` 的预设，仅展示可用的模式，避免选到半残的 preset
- 空白会话原地切换：当 `session.blank === true` 时直接调用 host 切换接口、不动会话视图
- 有历史会话安全切换：当 `session.blank === false` 时先清空客户端当前会话，再调用 `workspaces.startSession` 在同一工作区路径下开启一个新空白会话，然后把所选模式应用到那个新会话上；原有对话历史落到 host 端真实会话列表里，客户端只是换到新会话
- 智能隐藏：运行时可用预设不足两个时下拉不渲染（`modes.length < 2` 即返回 `null`），避免出现"列表里只有一项"的死控件
- 切换期间禁用：切换中下拉变 disabled、鼠标变 wait；切换出错（10 秒内没等到新会话、或 host 返回错误）时把错误文案写到 `title` tooltip，下次切换即清掉

## 技术实现
- **语言**: TypeScript（ESM；`"type": "module"`），host / client 双半区编译（`tsc -b` + `tsdown`），React 18 用于浏览器渲染
- **关键依赖**: `@deepseek-ai/dsh-client-runtime`（注入 client services）、`@deepseek-ai/dsh-client-ui-slots`（slot 注入目标 `conversation.session.header.actions`）、`@deepseek-ai/dsh-client-ui-conversation`（会话组件上下文）、`react ^18.2.0`；host 半区仅声明 cordis 接口而无任何运行时依赖
- **架构模式**: Cordis 双半区插件，但 client-only：`src/index.ts` 是空 host 入口（`apply` 是 no-op），所有功能集中在 `src/client/`；`apply(ctx)` 从 `ctx.get('connection').api.agentPresets` 拿到 RPC 句柄，从 `ctx.sessions` / `ctx.workspaces` 拿到会话/工作区快照，再把 `ModeSwitcher` 组件挂到 `conversation.session.header.actions` slot（order = -9，packages/dsh-mode-switcher/src/client/index.ts:10-26）；激活走 `cordis.patch.yml` 把 `ui-mode-switcher` row 注入 web profile
- **入口文件**: `packages/dsh-mode-switcher/src/client/index.ts`（browser 半区入口）+ `packages/dsh-mode-switcher/src/client/mode-controller.ts`（核心切换逻辑）+ `packages/dsh-mode-switcher/src/client/ModeSwitcher.tsx`（React 视图）

## 适用场景
你经常在同一个会话里需要切换 Plan / Code / 等不同的工作模式，但不想手动编辑配置文件、不想关掉当前对话重开新会话；插件会在你已经聊了一半的会话里主动开一个新空白会话并套上你选的新模式，旧对话留在 host 端不动。也适合只想用 DSH 官方运行时暴露的那几种 preset、觉得「会话里没法切模式」是缺失功能的用户。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DeepSeek Harness（web profile） | `^0.1.0-rc.7` | peerDependencies 全锁此版本（packages/dsh-mode-switcher/package.json:34-40）；cordis.patch.yml:1-3 把 `ui-mode-switcher` row 注入 web profile 名册 |
| Node.js | `^22.19.0 \|\| >=24.0.0` | 包内未独立声明 engines；与根 package.json#engines 全仓一致 |
| 平台 | 跨平台（macOS / Windows / Linux） | 仅 Web GUI 注入（`platform: "web"`，packages/dsh-mode-switcher/package.json:24），无 node-gyp / 原生绑定 |
| React | `^18.2.0` | peer 依赖（packages/dsh-mode-switcher/package.json:41） |

## 安装方式
```bash
dsh plugin --profile web add github:ningbainb/deepseek-harness-desktop/packages/dsh-mode-switcher
```

安装后重启 `dsh web`，打开任意会话即可在聊天区顶部看到胶囊样式的模式切换下拉。

## 配置项
本插件无需额外配置。运行时不会读取 `ctx.config`、不暴露 cordis Schema、不读环境变量；可调参数（`timeoutMs`，控制等待新会话的超时）只通过 `ModeSwitcherController` 的构造函数传入，不走插件配置。

| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| （无用户级配置项） | — | 源码里所有可调参数都是开发期构造注入，运行时不通过插件配置面板暴露 | — |

## 常见问题

**Q: 装上后能看到什么？**

A: 在 `dsh web` 打开的任意会话顶部，多出一个胶囊样式的下拉框，列出当前 DSH 运行时暴露的所有可用智能体模式（如 Plan / Code / 等）；选中后会立刻切换当前会话的工作模式。运行时可用预设不足两个时这个下拉会自动隐藏，避免出现无意义控件（packages/dsh-mode-switcher/src/client/ModeSwitcher.tsx:26、README.md:14）。

**Q: 切换模式会不会清空当前对话历史？**

A: 看情况。空白会话（还没有任何消息）时直接在原会话上原地切换；只要当前会话已有消息，插件会先清空当前客户端的会话视图、并调用工作区服务在同一路径下创建一个全新空白会话，再把所选模式套到新会话上（packages/dsh-mode-switcher/src/client/mode-controller.ts:53-70、tests/mode-controller.spec.ts:41-49）。历史数据落到 host 进程的真实会话列表里、不会因此被删除，只是当前聊天视图换到新会话。

**Q: 哪些模式会被列出来？**

A: 列表来自 DSH 官方运行时（packages/dsh-mode-switcher/src/client/mode-controller.ts:39-51 调用 `api.agentPresets.list`）。预设里只要带有 `broken` 字段就会被过滤掉；如果某个预设没有 `name`，则回退用其 `id` 作为显示文本（packages/dsh-mode-switcher/src/client/mode-controller.ts:44-50）。

**Q: 切换失败会怎么提示？**

A: 错误不会阻塞 UI，但会被显示在下拉框的 tooltip（`title` 属性）里；下一次切换时该提示会被清掉（packages/dsh-mode-switcher/src/client/ModeSwitcher.tsx:30-40）。同时切换进行时下拉框会被禁用、鼠标变 wait 光标，避免重复点击。

**Q: 等不到新会话怎么办？**

A: 控制器内部默认 10 秒超时（`timeoutMs` 默认值，packages/dsh-mode-switcher/src/client/mode-controller.ts:73）。超时后会以 "timed out while starting the new mode session" 抛错，并被同一份错误处理逻辑写到 tooltip 里；用户可重新尝试或刷新页面。

**Q: 跟官方 DSH 模式管理冲突吗？**

A: 不冲突。本插件是一个纯浏览器侧的 slot 注入器，把"模式切换"动作挂到 `conversation.session.header.actions` 槽位（packages/dsh-mode-switcher/src/client/index.ts:20-25），并不修改官方会话数据模型或运行时。卸载后下次 dsh web 启动时该下拉不再出现。

**Q: 这个插件只在 DSH Web 上能用吗？Headless / CLI 模式呢？**

A: 是，仅 DSH Web GUI（profile=web）。`package.json#dsh.client.platform` 声明 `web`，`cordis.patch.yml` 把插件 row 插入 web profile 名册；host 半区是空 no-op（packages/dsh-mode-switcher/src/index.ts:5-6），所有行为都在浏览器半区，因此 CLI/headless 模式下不会注入这个下拉。

## 上手难度
入门 — 一行安装命令、重启 dsh web 即生效；无需配置；用户只需要在会话顶部下拉里挑一个模式。所有"原地切换 vs 新建会话"、"过滤坏 preset"、"10 秒超时"等行为都由插件内置，编辑器无需介入。

## 已知问题与限制
- 错误只能被动查看：运行时错误通过下拉框 `title` tooltip 暴露，没有 toast、没有日志入口（packages/dsh-mode-switcher/src/client/ModeSwitcher.tsx:30-40）。如果失败频繁、又看不清 tooltip 内容，需要打开 DevTools 才能看到原始报错文本
- 仅 web profile：`package.json#dsh.client.platform` 锁 `web`，headless / CLI 模式下整个客户端注入（`apply`）都不会被调用，host 半区又是 no-op（packages/dsh-mode-switcher/src/index.ts:5-6），因此在桌面端非 Web 入口里看不到这个下拉
- 切换超时为硬编码 10 秒：`mode-controller.ts:73` 的 `timeoutMs ?? 10_000` 不可由用户在 DSH 侧配置；如果你的工作区有非常慢的初始化（极冷缓存），可能需要靠刷新重试
- 新会话 ID 解析依赖 `state.byId[id].blank === true` 判定（packages/dsh-mode-switcher/src/client/mode-controller.ts:87），如果 host 端会话状态里没正确打 blank 标记，等待会超时失败
- 仅当 DSH 运行时 preset 数量 ≥ 2 时下拉才显示；如果运行时只暴露一个或零个可用 preset，下拉完全隐藏，UI 上没有任何"无 preset 可用"的提示位（packages/dsh-mode-switcher/src/client/ModeSwitcher.tsx:26）
- preset 显示文本降级：预设没有 `name` 字段时会回退显示 `id`，对用户来说可能不够友好（packages/dsh-mode-switcher/src/client/mode-controller.ts:48）
- 不进入模型可见面：本插件是纯 UI 行为，切换不会写进 systemPrompt、不会出现在 agent 的上下文里；如果想让 agent 知道当前用的是哪个模式，需要另外的扩展

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [deepseek-harness-desktop](https://deepseek-plugin.org/plugins/ningbainb/deepseek-harness-desktop/packages/dsh-mode-switcher)
Wiki generated by AI (model: `MiniMax-M3`)
