English · 简体中文

# dsh-workspace-groups ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![DSH](https://img.shields.io/badge/DSH-0.1.1--rc.2-purple.svg) DeepSeek Harness plugin > **DeepSeek Harness(DSH)Web 客户端插件:完整的工作区分组管理工具。** > 把 GUI 侧边栏的工作区列表从两层「项目 → 会话」升级为三层 > **「分类文件夹 → 项目文件夹 → 会话」**,并围绕它提供完整的分组管理能力—— > 手动建组、重命名/删除分组、拖拽归类、项目与分组的自由排序、规则自动归类、 > 树形搜索。所有操作**即时生效并持久化**,对官方数据**零侵入**。 典型场景:多个 DSH 插件项目(SkillsManagePlugins / Documentation-Driven AI Coding / DeepSeek峰谷小组件 等)归入一个「DSH 插件」分类文件夹,点开是各项目,再点开是各自会话; 临时项目随手建个「临时」分组拖进去,用完删除分组,项目全部回归**顶层**。 ## 截图 dsh-workspace-groups 工作区分组管理 ## 特性 ### 分组树浏览 - **分组文件夹 → 项目文件夹 → 会话行**,分组/项目均可折叠;展开状态独立持久化 (`dsh.workspace.groups.view.v1`,刷新/重启保留) - **顶层项目行**:不归组的项目(不匹配任何规则、被移出分组、删除分组后回归的) 直接显示在分组列表之后,与分组平级——**没有「未分类」桶** ### 分组管理(完整生命周期) - **手动新建分组**:区头「新建分组」按钮即建即显,空分组也渲染 - **重命名 / 删除任意分组**:每个分组行(**含规则分类**)悬停 `⋯` 菜单; 删除分组后组内所有项目回到**顶层**;规则分类的改名/删除经 overlay 生效 (`renamed` / `hidden`),**规则 YAML 原样保留** - **规则自动归类**:sidecar YAML 声明分类规则(`pathPrefix` / `pathExact` / `nameContains` / `basenameContains`),改配置即可调整归类,无需改代码 ### 拖拽归类 + 排序 - **拖项目进分组**:拖到任意分组行 / 分组内项目行即移入(跨组移动 = 覆盖规则归类) - **从分组拖出**:拖动时**整个顶层区域都是移出落点**,用**插入横线**指示(非高亮框)—— 拖到任意顶层项目行(插到它前/后)、拖到最后一行下方空白(追加末尾)、顶层为空时 在最后一个分组行下方显示独立横线 = 移出分组;分组内项目行的菜单「移出分组」 (规则归类项目也有) - **项目组内排序**:拖到项目行上半 = 插到它前面、下半 = 插到它后面 - **顶层项目排序**:顶层项目行也可拖拽排序(上半 = 插到它前、下半 = 插到它后), 顺序持久化在 `workspaceOrder["__topLevel__"]` - **分组排序**:分组行可拖动,拖到另一分组行上半 = 移到它前面、下半 = 移到它后面 - **插入位置指示线**:拖动中实时显示 2px 指示线(行上/下方),松手落点所见即所得 - **按级别收起 + 结束后恢复**:拖项目只收起项目行(分组内 + 顶层,分组行不收); 拖分组只收起分组行(项目行不收);dragend 自动恢复拖动前的展开状态 - **行图标可区分**:分组行是文件夹图标、项目行是项目符号图标(官方同款), 分组与项目一眼可分 ### 搜索与操作 - **树形搜索**:命中后仍保留三层树结构(分类 → 项目 → 命中会话),命中行高亮 + 内容摘要,防抖 250ms - **工作区/会话操作不退化**:Add Workspace、项目重命名/删除、新建/打开/重命名/ 派生/归档会话 ### 持久化与零侵入 - 所有手动操作(分组、归类、排序、改名、隐藏)写入插件自有 overlay (`~/.dsh/workspace-groups.manual.json`),host 校验后**原子写入**(写坏返回 400 并 保留原文件) - **零侵入**:不修改 `~/.dsh/storages/workspace.json`、不修改会话落盘结构、不修改官方 `@deepseek-ai/dsh-client-ui-workspace` 包;规则 YAML 永不改写 - **产物自包含**:`lib/` 已构建并随仓库分发,Git 安装无需执行任何依赖脚本 ## 工作原理 - 本插件是 **client 插件**,注册进官方 sidebar shell 声明的 `sidebar.workspaces` slot(`kind: 'single'`),以 `priority: -1` 顶替官方默认 WorkspaceBrowser (官方以 priority 0 注册;single 槽位最低 priority 胜出)。 - 数据源全部复用运行时 API:`useWorkspaces` / `useSessions` 全局 hooks 与 `ctx.workspaces.*` / `ctx.sessions.*`,分类只是**展示层变换**。 - host 半做两件事:把 sidecar YAML 解析为 JSON 与运行时 overlay 合并,经 `GET /workspace-groups/config` 路由(`Cache-Control: no-cache`)供 client 获取; `PUT /workspace-groups/manual` 接收整份 overlay(手动分组、每工作区归类覆盖、 分组/项目排序、规则分类改名与隐藏),校验后原子写入 `$DSH_HOME/workspace-groups.manual.json`。 - **归类优先级**:手动覆盖(拖拽/菜单写入;`null` = 强制顶层、规则匹配也无效)→ YAML 规则自动归类(被隐藏的规则分类失效)→ **顶层**(不归组的项目显示为顶层行)。 YAML 永不改写。 ## 安装(GitHub 分发) > 前置:已安装 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) > (`dsh` 命令可用),并已初始化好目标 profile(如内置 `web`)。 ```sh dsh plugin --profile web add github:z-col/dsh-workspace-groups ``` 这会自动: 1. 在 `~/.dsh/profiles/web/package.json` 的 `dependencies` 加入 `"dsh-workspace-groups": "github:z-col/dsh-workspace-groups"`(含版本/commit) 2. 在 `dsh.profile.bundles` 末尾追加 `"dsh-workspace-groups"` 3. 运行 pnpm 安装并校验 bundle 层 **安装后重启 web profile**(bundle 与 host 半只有在重启后才会被加载): ```sh # 停止现有 dsh web 进程后重新启动,例如: dsh web ``` 验证安装: ```sh dsh --profile web --dump-config | grep -A3 workspace-groups # 应出现 - id: workspace-groups / name: dsh-workspace-groups / config: {} curl http://127.0.0.1:3080/workspace-groups/config # 应返回 sidecar YAML 解析后的 JSON ``` ## 卸载 ```sh dsh plugin --profile web remove dsh-workspace-groups ``` 这会自动从 `dependencies` 删除该依赖并从 `dsh.profile.bundles` 移除对应行。 同样需要**重启 web profile** 后生效。 > 手动等价做法(任选其一,不要重复):编辑 `~/.dsh/profiles/web/package.json`, > 从 `dependencies` 删除 `dsh-workspace-groups` 行、从 `dsh.profile.bundles` > 删除 `"dsh-workspace-groups"`,然后在该目录 `pnpm install`。 ## 分类配置(sidecar) 默认位置 `~/.dsh/workspace-groups.yaml`(也可用 `$DSH_HOME` 环境变量覆盖家目录)。 模板见仓库根目录 `workspace-groups.example.yaml`。 ```yaml categories: - name: DSH 插件 rules: - pathPrefix: /Users/zcol/Project/SkillsManagePlugins - nameContains: 插件 - basenameContains: plugin - name: 个人项目 rules: - pathPrefix: /Users/zcol/Project/yeluzi ``` 规则字段(每个 rule 是 OR 关系,任一命中即归类;分类按序匹配,先到先得): | 字段 | 含义 | |---|---| | `pathPrefix` | 项目绝对路径前缀 | | `pathExact` | 项目绝对路径精确匹配 | | `nameContains` | 项目显示标题包含(忽略大小写) | | `basenameContains` | 项目目录名包含(忽略大小写) | 未命中任何分类、或被移出分组的项目显示为**顶层项目行**(与分组平级),不会被隐藏。 ## 手动分组与拖拽归类(runtime overlay) 规则 YAML 之外,还有一份插件自有的运行时 overlay,**只记录 UI 里的手动操作**, 默认位置 `$DSH_HOME/workspace-groups.manual.json`(例如 `~/.dsh/workspace-groups.manual.json`): ```json { "categories": ["临时", "归档"], "assignments": { "a1b2c3d4-e5f6-7890-abcd-ef1234567890": "临时", "a1b2c3d4-e5f6-7890-abcd-ef1234567891": null }, "categoryOrder": ["临时", "DSH 插件"], "workspaceOrder": { "临时": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] }, "renamed": { "DSH 插件": "插件集" }, "hidden": ["文档"] } ``` - `categories` —— 手动新建的分组名(无规则,空分组也渲染)。 - `assignments` —— 工作区 → 分组的归类覆盖,键是稳定的工作区 id(重命名不影响)。 **优先级高于 YAML 规则**;值为 `null` 表示**强制移到顶层**(即使规则能匹配)。 - `categoryOrder` —— 分组显示顺序(顶层项目不在此列,恒显示在分组之后)。 - `workspaceOrder` —— 每个分组内项目的手动排序(拖拽排序写入)。 - `renamed` / `hidden` —— 规则分类的 UI 改名/删除(隐藏后其规则失效,匹配项目 变顶层);规则 YAML 原样保留。 - 文件由浏览器 UI 全量写入(`PUT /workspace-groups/manual`,原子替换),手工编辑 同样生效(下次加载时读取);写坏会返回 400 并保留原文件,不会破坏规则 YAML。 | 操作 | 入口 | |---|---| | 新建分组 | 区头「新建分组」按钮(文件夹图标),弹窗输入名称 | | 重命名/删除分组 | **任意分组**(含规则分类)悬停 `⋯` 菜单;删除后组内项目回顶层 | | 拖项目进分组 | 拖动项目行到目标分组行 / 分组内任意项目行,松手即移入 | | 项目排序 | 拖动项目行到同组另一项目行:**上半 = 插到它前、下半 = 插到它后**(指示线显示落点);拖动时所有项目行收起、dragend 恢复 | | 顶层排序 | 拖动顶层项目行到另一顶层行:**上半 = 插到它前、下半 = 插到它后**;顺序持久化 `workspaceOrder["__topLevel__"]` | | 移出分组 | 拖到**顶层区域任意位置**(用插入横线指示落点——拖到顶层行前/后、最后一行下方追加、顶层为空时最后分组行下方独立横线),或项目行菜单「移出分组」(强制移到顶层) | | 分组排序 | 拖动分组行到另一分组行:**上半 = 移到它前、下半 = 移到它后**(指示线显示落点;拖动时所有分组收起、dragend 恢复) | ## 收录标签(topics) 本仓库面向 DSH 插件生态的自动收录(社区市场靠 GitHub topic 扫描发现),已设置: - `dsh-plugin`(核心标签,[1024Store](https://github.com/imsai-sh/awesome-deepseek-harness-plugins) 等市场定时按此 topic 自动发现,并校验 `package.json` + 插件 bundle 清单 (`cordis.patch.yml`)) - `deepseek-harness` / `deepseek-harness-plugin` / `dsh` - `sidebar` / `workspace` / `workspace-groups` `package.json` 同时提供 `keywords` 便于 npm/搜索索引。 ## 开发 ```sh pnpm install pnpm typecheck # host + client 双 program 类型检查 pnpm test # 核心规则、overlay、树派生单测 pnpm build # 构建 lib/(node 半 + client bundle) pnpm watch # tsdown 监听(client HMR) node scripts/verify-groups.mjs # 真机 CDP 验证(host 已重启时;自启独立 headless Chrome,自动恢复现场) ``` 产物契约(与官方 client 包一致): - `lib/index.js` —— host 半(ESM;读取 sidecar + `/workspace-groups/config` 路由, js-yaml 已内联,无运行时依赖) - `lib/client.js` —— browser 半(`window.__ModuleLoader__.load({id, factory})`; 仅 require 平台 seed:react / react/jsx-runtime / @deepseek-ai/dsh-client-runtime/client / @deepseek-ai/dsh-client-ui-primitives;跨插件值 import 在构建期被 purity 门拒绝) - `lib/types/**` —— 声明文件 > 发布策略说明:`lib/` 构建产物随仓库提交(无 `prepare` 脚本),因此 > `dsh plugin add github:...` 全程无需执行第三方构建脚本,安装即用。 ## 目录结构 ``` src/ index.ts # host 半:config 快照路由 + manual 写路由 host-config.ts # sidecar YAML 读取/校验 host-manual.ts # runtime overlay 读写/校验(原子发布) context-types.ts # host 侧 cordis 服务结构类型 core/ types.ts # 配置类型(两半共享) matcher.ts # 分类规则 + 手动覆盖优先级 + 排序纯函数(两半共享) client/ index.ts # apply:注册 sidebar.workspaces(priority -1) contract.ts # 注入面类型 stores.ts # 展开状态 store(persist: dsh.workspace.groups.view.v1) tree.ts # 三层树派生 + 树形搜索派生 GroupsBrowser.tsx # 浏览区域组件(分组弹窗 + 拖拽归类/排序 + 插入指示线) rows.tsx # 分类/项目/会话/搜索结果行(拖拽源/目标) locales.ts # 中英文案 styles.css # 内联样式 tests/ core.test.ts # 分类规则 + 手动覆盖优先级 + moveBefore/moveAfter + 配置解析 manual.test.ts # overlay 校验 + 文件原子往返 tree.test.ts # 树派生渲染契约(手动分组空渲染/覆盖优先) store.test.ts # 展开状态语义(折叠写 false 不删 key) scripts/ verify-groups.mjs # 真机 CDP 验证(自启 headless Chrome,自动恢复现场) ``` > 开发文档(`docs/` 五级框架与 `AGENTS.md`)是开发用工程文件,**不随仓库分发** > (已在 `.gitignore` 排除)。 ## 验证记录 - v0.1/v0.2 真实组合验证(headless Chrome + CDP 实操):三层树顶替生效、分类正确、 展开持久化、搜索保留归属;`workspace.json` / 会话落盘 / 官方 store 零侵入。 - v0.3 真机验证 24/24(`scripts/verify-groups.mjs`:建组/拖拽/排序/收起/规则分类 菜单/重命名/删除回顶层/现场恢复,零侵入断言)。 - v0.4 真机验证 30/30(新增:插入指示线、项目/分组**向下拖**(行下半 → 插到目标 之后)、分组向上拖(行上半 → 移到目标之前);现场恢复通过)。 - v0.4.1 真机验证 34/34(新增:**从分组拖出**——顶层落点区/顶层行 drop = 强制顶层;分组内项目行菜单「移出分组」)。 - v0.5 真机验证 35/35(模型变更:**无「未分类」桶**——顶层项目行、删除分组成员 回顶层、拖拽/菜单移出到顶层、树中不存在未分类桶;现场恢复)。 - v0.6 真机验证 40/40(新增:**按级别收起**——拖项目只收起项目行(分组行不收)、 拖分组只收起分组行(项目行不收);dragend 还原拖动前展开快照)。 - v0.6.1 真机验证 42/42(新增:**整个顶层区域为移出落点**且拖拽中有可见高亮提示; 分组/项目行图标区分;现场恢复)。 - v0.7 真机验证 46/46(新增:顶层落点改用**插入横线**(非高亮框)——拖到顶层行前/后、 最后一行下方追加、顶层为空时独立横线;**顶层项目可排序**,顺序持久化 `workspaceOrder["__topLevel__"]`;并修复 host 校验拒绝 `__topLevel__` 与 「拖到两行之间却落在上一行上面」的落点定位 bug;现场恢复)。 - 单测 66 用例全绿(vitest:`core` / `manual` / `tree` / `store`)。 - 可复跑的自动化真机验证:`node scripts/verify-groups.mjs`(需 host 已重启)。 ## License MIT