# deepseek-harness-desktop

> 为 DSH Web 界面在官方工作区胶囊旁加 git 分支选择器与 Git 图谱面板，分支切换在 host 进程真实执行，并加 loopback + 工作区门卫。

## 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-git-graph
```

## Wiki

## 一句话定位
为 DeepSeek Harness Web 界面在官方工作区胶囊旁加一个 git 分支选择器，并在弹层里提供「创建并检出新分支」与「Git 图谱」面板，分支切换在 host 进程真实执行，浏览器只负责展示与点选。

## 核心能力
- 在会话输入卡正上方的 chip 上显示当前分支名（detached HEAD 显示「分离 HEAD」），非 git 工作区自动隐藏，避免出现死控件
- 打开弹层后可搜索本地分支、查看当前分支的勾选标记，并在底部看到「未提交的更改」摘要（dirty 文件数）
- 切换分支前自动跑守卫检查（未解决冲突、进行中的合并/rebase/cherry-pick/revert/bisect、目标分支被其他 worktree 检出），守卫命中时切换会被拒绝并给出可读的中英错误提示，不会破坏磁盘工作树
- 支持「创建并检出新分支」，先在前端镜像 `git check-ref-format --branch` 的命名规则即时反馈，再走 host 端权威校验和重名检查，最后执行 `git switch -c <name>`，成功后自动刷新 chip 与图谱
- 提供只读 Git 图谱弹层：以拓扑顺序展示分支/标签/远端的 commit 列表，含等宽字体的泳道字符、相对时间（刚刚 / X 分钟前 / X 小时前 / X 天前）、ref 标签，分页加载（每页 100 条，初始 200 条）
- host 端通过 `/git/events` SSE 推送外部 git 状态变化（订阅时每 30 秒轮询一次，单次探测 15 秒超时）；chip 还会在 window focus 时刷新（5 秒节流），保证另开终端切换分支后 UI 能跟上

## 技术实现
- **语言**: TypeScript（host 与 browser 半区共享 src/core 纯逻辑；TypeScript 模块化构建，css-modules 走 lightningcss）
- **关键依赖**: `@deepseek-ai/dsh-client-ui-conversation`、`@deepseek-ai/dsh-client-ui-slots`、`@deepseek-ai/dsh-client-runtime`、`@deepseek-ai/dsh-client-locale`（peer 注入面，皆 ^0.1.0-rc.7，package.json:60-70）；host 侧依赖 `@deepseek-ai/dsh-host-webserver`、`@deepseek-ai/dsh-subprocess`、`@deepseek-ai/dsh-workspace`（package.json:61-70）；`@deepseek-ai/cordis` 4.x 做 function plugin 装配
- **架构模式**: 双面 cordis 插件。host half（src/index.ts）通过 `inject = ['webServer','subprocess','workspaceRegistry']` 挂载 GitService 与 `/git/*` 路由；client half（src/client/index.ts）通过 `inject = ['slots','sessions','connection','locale','conversation']` 注入 git verbs（repoStatus / branches / switchBranch / createBranch / graph / subscribeChanges）。激活走 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 把 `ui-git-graph` row 注入 profile（cordis.patch.yml:1-12）
- **入口文件**: `packages/dsh-git-graph/src/index.ts`（host 半区 apply 入口）+ `packages/dsh-git-graph/src/client/index.ts`（browser 半区 apply 入口）。git 命令 argv 集中在 `src/core/git-command.ts`，纯分支名校验 mirror 在同一文件

## 适用场景
当你在 DeepSeek Harness Web 里处理多个 git 分支、经常需要从对话界面里直接切到别的分支去看历史/对比、又不想打开终端；同时你希望切换前有冲突保护，避免「切过去发现还有未解决的合并冲突」导致工作树脏掉。也适合作为只读 Git 图谱的轻量替代——你不必离开 dsh Web 就能看到全分支/标签/远端的拓扑结构与 ref 标签。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DeepSeek Harness（web profile） | `^0.1.0-rc.7` | peerDependencies 全锁此版本（package.json:60-70）；bundled patch 走 cordis.patch.yml（cordis.patch.yml:1-12） |
| Node.js | `^22.19.0 \|\| >=24.0.0` | package.json:7-9 `engines.node` |
| 平台 | macOS / Windows / Linux | 跨平台，无原生模块（package.json 仅声明 react ^18.2.0 peer，无 node-gyp 依赖） |
| 系统 git 可执行 | 必需 | host 半区通过 `ctx.subprocess.spawn(['git',...])`（Windows 强制 `git.exe`）真实执行 git 命令（src/host/git-service.ts:53-94）；无 git 则所有操作以 internal 错误返回 |
| 已注册的工作区 | 必需 | /git/* 路径门卫：请求的 path 必须 realpath 后命中 ctx.workspaceRegistry 里的某个工作区（src/index.ts:35-48）；浏览器对未注册目录发起请求会得到 workspace-unknown |
| Loopback 客户端 | 必需 | /git/* 与 /git/events SSE 都拒绝非 loopback socket/Host 请求（src/host/routes.ts:83-103），LAN 暴露的 dsh web 对外网客户端一律 403 |
| DSH_HOME 环境变量 | 可选 | 未设置时回退到 `~/.dsh`，host 半区把 worktree 目录放在 `$DSH_HOME/worktrees/<repoHash>/<runId>` 下（src/index.ts:82 / src/host/worktree-service.ts:212） |

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

## 配置项
本插件无需额外配置。所有守卫、SSE 节奏、上下文挂载超时都在代码里固定写死，不开放用户级 schema。

| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `DSH_HOME` | 环境变量（可选） | 决定 worktree 子目录的存放位置；不设时使用 `~/.dsh`（src/index.ts:82） | `~/.dsh` |

源码里其他可调参数（仅供二次开发参考，运行时不通过插件配置）：
- `CONTEXT_FALLBACK_MS = 2000`：等待 `conversation.input.selector.context` 槽位声明的超时，超时后 chip 回退到 `conversation.input.dock`（src/client/index.ts:108）
- `POLL_INTERVAL_MS = 30_000`：host SSE 在有订阅者时轮询 workspace 状态的时间间隔（src/host/routes.ts:45）
- `STATUS_TIMEOUT_MS = 15_000`：单次状态探测的硬超时，避免挂死的 git 子进程卡住推送流（src/host/routes.ts:57）
- `FOCUS_REFRESH_MIN_MS = 5_000`：window focus 触发的 chip 重新拉取节流（src/client/chips/BranchChip.tsx:37）

## 常见问题

**Q: 这个插件会改动 DeepSeek Harness 官方源码吗？**

A: 不会。AGENTS.md:5-6 明确「主仓（sibling checkout）零改动；本仓是自包含的 cordis 插件包」；所有类型来源是 node_modules 里的 `@deepseek-ai/*` peerDependencies（package.json:60-70），不引入对 DSH 源码 checkout 的 tsconfig 引用。卸载即回到官方原生行为。

**Q: 分支切换是真实的 git switch 吗？会影响其他会话吗？**

A: 是真实的。src/host/git-service.ts:176-197 调用 `git switch --no-guess <branch>` 跑在 repoRoot 真实工作树上，并且会作用于该 workspace 下所有会话（不是单会话的 cwd 覆盖）。这意味着如果你在一个会话里切了分支，另一个会话来打开同一个工作区时也已经是新分支了；这是「工作区级」语义，不是「会话级」。

**Q: 浏览器能不能借这个接口对任意目录跑 git？**

A: 不能。src/index.ts:35-48 的 workspaceGate 把请求路径 realpath 后必须命中 ctx.workspaceRegistry 里某个已注册路径，否则返回 workspace-unknown；routes.ts:83-103 还会拒绝所有非 loopback 客户端（LAN 暴露的 dsh web 对外部客户端直接 403），/git/* 还强制 POST + application/json（routes.ts:215-229）。Worktree 路由更严：只接受不透明 ID，路径/base ref/argv 一律不接（README.md:74 / worktree-routes.ts:46-48）。

**Q: 什么时候分支 chip 会隐藏？**

A: 当它查不到 cwd（sessions list 里该 sessionId 的 cwd 为空）或者 status 返回 null（非 git 工作区）时整个 chip 不渲染。src/client/chips/BranchChip.tsx:253 写「repo === undefined || repo === null return null」；这种隐藏优于禁用，避免死控件，且工作区变仓库后 chip 会在下一次刷新时自动出现。

**Q: 切换失败会给出什么样的错误？**

A: 错误分两大类：守卫级（conflicts-present、operation-in-progress、branch-in-other-worktree、invalid-branch-name、branch-already-exists、target-branch-not-found、workspace-unknown）和切换时 Git 抛出的覆盖冲突（tracked/untracked-changes-would-be-overwritten，附前 2 个文件路径 + 溢出计数）。src/host/git-service.ts:294-311 的 guardBlock 跑前一种，src/core/git-command.ts:178-196 的 classifySwitchFailure 把 stderr 归到稳定 code；客户端在 src/client/chips/error-copy.ts:27-50 把 code 翻成中英双语可读句子。

**Q: 跟官方分支管理有冲突吗？会被重复工作区选择器覆盖吗？**

A: 工作区选择不在本插件里——官方工作区胶囊是唯一入口（src/client/index.ts:23-24 / ADR-001:36）。插件只补「分支」和「图谱」两个动作，对应 UI 上与官方胶囊并排的 28px 透明 chip。分支状态不会写进 session log，也不会进入模型可见面（src/index.ts:6-8），所以不会影响模型侧的对话历史。

**Q: 需要 Git 在系统 PATH 里吗？**

A: 需要。Host 侧的 GitService 用 subprocess 服务直接 spawn `git`（macOS/Linux）或 `git.exe`（Windows，src/host/git-service.ts:53-55 强制走原生可执行名避免 .cmd shim 解析问题）；运行机器必须装好 git，否则所有 /git/* 接口都会以 internal 错误返回。Worktree 操作走同一个 spawn seam。

**Q: 怎么卸载？**

A: `dsh plugin --profile web remove @linxin666/dsh-client-ui-git-graph`（README.md:67）。包名是 npm 发布的 `@linxin666/dsh-client-ui-git-graph`，激活插件时挂的是 `ui-git-graph` 这个 cordis row 名（cordis.patch.yml:11）。卸载后下次 dsh web 启动就不再注册任何 /git/* 路由与浏览器 chip。

## 上手难度
入门 — 安装一行命令，重启 dsh web 即生效；无需配置；用户只需点击 chip、选择分支、查看错误提示。所有高级行为（守卫、SSE 节奏、loopback 限制）都由插件内置。

## 已知问题与限制
- 仅支持本地分支：分支列表只走 `git for-each-ref refs/heads`（src/host/git-service.ts:151 / src/core/git-command.ts:19-23），不会列出远端分支。如果你在做 `git fetch` 后想看 origin/* 分支，需要在终端 fetch 后让 chip 重新拉取（挂载/弹层打开/focus 都会触发）
- 切换窗口受工作区状态强约束：未解决合并冲突、进行中的 merge/rebase/cherry-pick/revert/bisect、目标分支被其他 worktree 检出，这三种情况都会被守卫拦下（src/host/git-service.ts:294-311），你需要先去终端完成或 abort 这些操作
- LAN 暴露时不服务 /git/*：所有 /git/* 接口强制 loopback + Host 校验（src/host/routes.ts:83-103），如果你把 dsh web 反向代理到公网，对应接口会返回 403；插件不打算为外部访问放宽这个限制（ADR-001:38）
- Worktree 功能只在 host half 暴露（src/index.ts:62-87），浏览器侧 chip 不带 worktree 入口；这部分由桌面端 Task Board 这类插件另行消费 /git-worktree/* 路由
- Worktree 子目录固定在 `$DSH_HOME/worktrees/<repoHash>/<runId>` 下（src/host/worktree-service.ts:212 / src/index.ts:82），且路径逃逸会被 TypeError 阻止（src/host/worktree-service.ts:214）；如果你移动了 DSH_HOME，需要保证旧路径下的 worktree 已被清理或主动移除，否则会出现 orphan 记录
- 浏览器 chip 与官方工作区胶囊是「会话级 vs workspace 级」分离的：会话切换不会清空 chip，但切到非 git cwd 时 chip 会自动隐藏（src/client/chips/BranchChip.tsx:253）
- package.json:7-9 把 Node 锁到 `^22.19.0 || >=24.0.0`，更早的 Node 不在测试矩阵内

---

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-git-graph)
Wiki generated by AI (model: `MiniMax-M3`)
