🗂️ 为 DeepSeek Harness 而生的左右侧栏
左侧 · 工作区/会话浏览 —— 右侧 · 文件 + Git 面板
功能亮点 · 安装 · 开发 · 架构 · 许可证 · English
--- > 🌐 **English**: 见 [README.en.md](README.en.md) · 简体中文见本文件 # dsh-plugin-sidebar 让 DeepSeek Harness 的**会话管理第一次像 IDE 一样顺手**。 左侧栏按工作区浏览全部会话(状态点、分组、搜索、一键操作),右侧栏在会话内就地浏览文件、检视 Git 变更、提交、切分支——装完即用,风格跟随 DSH 主题自动适配浅色/深色。 ## ✨ 功能亮点 ### 🧭 左侧栏 · 工作区/会话浏览 - **按工作区分组**:组头可折叠(文件夹图标 + 标题 + 计数 + chevron),当前会话卡片高亮 - **会话状态一眼可见**:运行中(绿色脉冲)/ 等待处理(琥珀)/ 完成 / 空闲,状态点 lane + 标题 chip - **卡片 hover 快捷操作**:重命名(行内编辑)/ 复制 fork / 归档 / **移动到文件夹** - **顶部搜索**:本地标题/cwd/工作区匹配 + 远端内容搜索合并(250ms 防抖),`Esc` 收起 - **组头 hover**:在此新建会话 / 重命名工作区 / 删除工作区 - **rail 窄栏模式**:侧栏折叠为图标列,点搜索/新建自动展开 ### 📁 右侧栏 · 文件浏览 + Git 面板 - **会话头部右上角开关**(与会话日志下载并列),**开关状态按会话记忆**:某会话开过右栏,切走再切回仍保持打开 - **文件浏览器**:懒加载目录树(展开才拉子列表)、按文件名搜索、按内容搜索(文件/行号/行内容)、**目录 git 状态聚合徽标**(子文件最高优先级 D>M>A>R>U) - **文件预览 tab(CodeMirror 编辑)**:主区域「对话 / 轨迹」之后新增**文件预览** tab,右侧栏点文件自动切换过去。文本文件用 **CodeMirror 6** 渲染——**语法高亮**(按扩展名自动选语言)+ **可编辑** + **保存写回磁盘**(`●` 未保存标记 + 保存按钮,头部固定始终可见);二进制/大文件给出提示 - **Git 面板**:状态分「已暂存 / 更改」两段(`--porcelain=v1 -z` NUL 解析)、着色 diff、提交框、分支切换 - **批量操作(VSCode 源码控制风格,悬停在 section 头显示)**:`暂存全部`(+) / `取消暂存全部`(−) / `放弃全部更改`(撤销);单文件行 hover:diff(文件) + 暂存(+)/取消暂存(−) + 放弃(撤销) - **提交历史(VSCode 源码控制风格)**:时间线圆点 + 竖线连接、提交信息单行截断,展开显示作者·日期 + 该提交**改动文件列表**(状态字母 M/A/D 着色),文件按需懒加载    ### 🔀 会话移动到文件夹 - 复制语义(fork):源会话保留,在目标工作区创建继承**全部已完成历史**的新会话,创建后自动打开 - 走官方 `agents.create(seed + meta.cwd)` 路径,新建的是 **agent**(生命周期归 agent 注册表,不随插件停止消失) - 目标工作区选择浮层:`createPortal` 到 body + `position: fixed` 锚定,脱离列表 `overflow` 裁剪,靠近视口底部自动向上展开 ### 🌏 为中文用户打磨 - 全中文界面 + English,跟随 DSH 语言偏好自动切换 - 全部使用 DSH 主题 token(`--dsw-alias-*` / `--ds-*`),浅色/深色自动适配 ## 📦 安装 从 GitHub 或本地路径安装。**建议先确认你的 dsh 版本,再按下方的「版本兼容」选择配套的插件版本**: ```bash # 从 GitHub 安装(推荐,按需指定版本) # 注意:pnpm 的 GitHub 版本锁定语法用 `#tag`,本文档旧写法 `@0.3.2` 会被 pnpm 误解析为不存在的仓库 null/0.3.2 而报错 dsh plugin --profile web add github:webkong/dsh-plugin-sidebar#v0.3.2 # 或安装最新主分支 dsh plugin --profile web add github:webkong/dsh-plugin-sidebar#main # 或从本地路径安装(开发) dsh plugin --profile web add /path/to/dsh-plugin-sidebar ``` 重启 dsh web 后,左侧栏接管 `sidebar.workspaces`,右侧栏接管 `details` 列,会话头部出现右栏开关。 > ⚠️ **生效方式**:安装 / 重启后生效;**Host 改动需重启 dsh web**,**Client 改动(`lib/client.js`)刷新页面即可**。 > 💡 **安装建议**: > - `dsh ≥ 0.1.2`(含 `0.1.2-alpha.3`):**务必用 `0.3.2`**,已适配 dsh 0.1.2 的客户端服务迁移与懒解析; > - `dsh 0.1.1-rc.x`:可用 `0.3.2` 或 `0.3.1`; > - 更旧的 dsh:回到 `≤ 0.2.x`(但 `dsh ≥ 0.1.2` 上旧版 client 无法加载)。 ## 🔖 版本兼容 **速查:按你的 dsh 版本 → 推荐插件版本** | 你的 dsh 版本 | 兼容的插件版本 | 建议 | | --- | --- | --- | | **dsh ≥ 0.1.2**(含 `0.1.2-alpha.3`) | **0.3.1 或更高** | **推荐 0.3.2**:已适配 dsh 0.1.2 的 `uiWorkspace` 服务迁移与懒解析 | | **dsh 0.1.1-rc.x** | 0.3.0 – 0.3.2 | 均可;`0.3.0` 已验证 `0.1.1-rc.2` | | **dsh ≤ 0.1.1-rc.x** | ≤ 0.2.x | 旧版;在 `dsh ≥ 0.1.2` 上 client 因等待已移除的 `dsh-client-runtime` 而无法加载 | **详表:按插件版本 → 兼容的 dsh 版本** | 插件版本 | 兼容的 dsh 版本 | 说明 | | --- | --- | --- | | **0.3.2** | dsh ≥ 0.1.1-rc.2 | 左侧栏层级样式优化:会话标题左侧状态点右移缩进,突出「文件夹 → 会话」的从属层级;会话标题字号由 13px 调小至 12px | | **0.3.1** | dsh ≥ 0.1.1-rc.2(已验证 `0.1.1-rc.2`、`0.1.2-alpha.1`、`0.1.2-alpha.3`) | 适配 dsh 0.1.2 客户端服务迁移:`startSession` / `pickDirectory` 改经 **`uiWorkspace`** 服务调用(原挂在 `workspaces`);`layout` / `sessions` / `workspaces` / `uiWorkspace` / `timer` 改为**调用时懒解析**(服务异步激活,不再在 apply 阶段缓存)。修复「右侧入口点击没反应」「左侧添加文件夹点击没反应」 | | **0.3.0** | dsh ≥ 0.1.1-rc.2(已验证 `0.1.1-rc.2`、`0.1.2-alpha.1`) | client `inject` 移除 `dsh-client-runtime`(该包自 dsh 0.1.2 起被移除);**未适配** 0.1.2 的 `uiWorkspace` 服务迁移,在 `0.1.2-alpha.3` 上部分点击可能无响应 | | **≤ 0.2.x** | dsh ≤ 0.1.1-rc.x | 旧版;`dsh ≥ 0.1.2` 上 client 因等待已移除的 `dsh-client-runtime` 而无法加载 | ## 🔧 开发 ```bash npm install # 安装 esbuild / typescript(仅开发期) npm run build # esbuild 打包 src/ → lib/(Host ESM + Client __ModuleLoader__ bundle) npm run typecheck # tsc --noEmit 严格类型检查 npm test # node --test 纯函数单元测试(42 用例) npm run check # typecheck + 产物语法检查 + 单元测试 ``` ### 结构(TypeScript 模块化) 源码为 TypeScript 模块化组织(参考官方 ui 插件结构),构建产物在 `lib/`: ``` dsh-plugin-sidebar/ ├── package.json # dsh.bundle / dsh.client 声明,scripts ├── cordis.patch.yml # bundle patch:装载 dsp-sidebar 行 ├── build.mjs # esbuild 构建(Host ESM + Client __ModuleLoader__ bundle) ├── tsconfig.json # 严格类型检查(node + DOM/React) ├── lib/ # 构建产物(已 gitignore) │ ├── index.js # Host 单文件 ESM bundle │ └── client.js # Client __ModuleLoader__ bundle ├── src/ │ ├── host/ # Host 源码(Node 环境) │ │ ├── index.ts # 入口:name/inject/apply + webServer 路由注册 │ │ ├── session.ts # 会话复制(移动到文件夹):readSession → cut → agents.create(seed+meta.cwd) │ │ ├── git.ts # Git 操作:runGit + porcelain/NUL/log 解析 │ │ ├── fs.ts # 文件系统操作:列目录 / 读文本(512KB 截断) │ │ ├── search.ts # 搜索:文件名递归匹配 + 内容逐行匹配 │ │ └── http.ts # JSON 响应 / loopback 校验 / 请求体 / 参数转义 │ └── client/ # Client 源码(DOM + React 环境) │ ├── index.ts # apply 入口:注入样式 / 注册词典 / 注册席位 │ ├── api.ts # /dsp-sidebar/api fetch 封装 │ ├── i18n.ts # 中英双语词典(NS 与键类型) │ ├── types.ts # 契约:会话/工作区数据面 + Host API 面 + git wire 形状 │ ├── util.ts # 共享工具:相对时间 / basename / 状态推导 │ ├── icons.tsx # 图标(lucide 风格描边 + 右侧栏面板填充图标) │ ├── previewStore.ts # 文件预览共享 store(sessionId 隔离,useSyncExternalStore 订阅) │ ├── preview/ # 主区域「文件预览」view │ │ └── PreviewView.tsx # conversation.view occupant:订阅 previewStore 渲染预览 │ ├── styles/ # 按组件域拆分 CSS + 聚合注入 │ │ ├── left.css # 左侧栏样式 │ │ ├── right.css # 右侧栏样式 │ │ ├── preview.css # 主区域预览 tab 样式(全局 dsw token) │ │ └── index.ts # injectStyles(幂等注入一个 style 标签) │ ├── left/ # 左侧栏(对齐官方 WorkspaceBrowser + rows/) │ │ ├── derive.ts # 数据推导:分组 / 搜索合并 │ │ ├── rows.tsx # 行组件:SessionCard / SearchRow / GroupSection(含移动浮层 portal) │ │ └── WorkspaceBrowser.tsx # 主组件:标题头 + 搜索 + 列表 + rail │ └── right/ # 右侧栏(对齐官方 RightSidebar + SourceControl + FileExplorer) │ ├── derive.ts # git 状态分类(badge / staged / unstaged / untracked / 目录聚合 dirBadge) │ ├── FilesPanel.tsx # 文件浏览器(懒加载树 + 搜索 + 预览 + git 徽标) │ ├── GitPanel.tsx # Git 面板(状态 / 暂存 / diff / 提交 / 时间线历史 / 分支) │ └── RightPanel.tsx # 面板外壳(活动条 + 标签切换)+ 头部开关 └── test/ # 纯函数单元测试(node --test) ``` ## 📡 通信契约 Host 通过 `webServer` 前缀路由 `/dsp-sidebar/api` 提供 HTTP API(仅限本机 loopback,POST + 方法名在路径末段),客户端用浏览器 `fetch` 调用: | 方法 | 说明 | | --- | --- | | `fs.list` | 列目录(`{path}`) | | `fs.read` | 读文本(512KB 截断,二进制返回 `kind:'binary'`) | | `fs.search` | 搜索(`{mode: 'name'\|'content', path, query}`) | | `fs.gitStatus` | 目录 git 状态映射(path → XY,供文件徽标) | | `git.status` | git 状态(`{cwd}`) | | `git.diff` | diff(`{cwd, path?, staged?}`) | | `git.stage` / `git.unstage` | 暂存 / 取消暂存(`{cwd, path?}`) | | `git.discard` | 放弃更改(`{cwd, path}`) | | `git.commit` | 提交(`{cwd, message}`) | | `git.log` | 提交历史(`{cwd, count?}`) | | `git.logFiles` | 某次提交改动文件(`{cwd, hash}`,name-status 解析) | | `git.branches` | 分支列表(`{cwd}`) | | `git.checkout` | 切换分支(`{cwd, branch}`) | | `session.copyTo` | 复制会话到目标工作区(`{srcId, targetPath}`)→ 返回 `{sessionId}` | ## 🏗 架构 - **Host**(`src/host/`):经 `webServer` 注册 `/dsp-sidebar/api` 前缀路由。文件操作走挂载的 `fs` 服务(`resolve` → `listDir`/`stat`/`readText`/`readBytes`,尊重沙箱与观测策略);Git 操作走挂载的 `shell` 服务(`resolve` + `run`,`git -C