DSH Plugins Marketplace

DSH Plugins

Plugins

/

dsh-session-multiselect

x

dsh-session-multiselect

Manifest valid

DSH Desktop plugin: multi-select panel for conversations ??grouped by workspace, batch delete / fork / export / summarize / pin / archive.

UI (client)hasBundlePatch

dsh-session-multiselect

test license: MIT node DSH Desktop

DSH Web 客户端的对话多选插件:一次勾选多个对话,然后批量删除 / Fork / 导出 / 汇总给 AI / 置顶 / 归档。

Multi-select for DSH Desktop conversations. A checkbox panel — grouped by workspace, with search, select-all/invert and Shift-range select — that runs batch actions over the official Session contracts (delete / fork / export Markdown+JSON / summarize into a new session / pin / archive / unread). Install as a standard DSH bundle: dsh plugin --profile web add . (see 安装). No build step, no host-side code, MIT licensed.

入口与做法:DSH 原生侧边栏的会话列表由 @deepseek-ai/dsh-client-ui-workspace 以 single slot 独占渲染, 没有提供逐行的扩展 slot,所以无法在原生会话行上直接加复选框。本插件的做法是:

  • 在会话列表工具栏那一行、搜索放大镜左侧注入一个 28px 的入口按钮,打开一个自带的完整多选面板; 这是整个插件唯一的 DOM 注入(官方没有 header slot,做法与踩过的坑见下文「入口位置」)
  • 面板、状态、批量动作全部走官方扩展面:sidebar.footer.action slot(注入失败时的兜底入口)、 sessions 服务、locale 字典、客户端 store —— 不改动 DSH 原生代码
  • 注入失败也不会失去功能:定位不到目标时入口自动退回侧边栏底部

兼容性:在 DSH Desktop 0.9.0 的 Web 客户端上开发并实测(Windows);插件只依赖官方已有的 slots / locale / sessions 三个服务,不依赖任何带构建哈希的类名以外的内部结构,DSH 升级导致的 最坏情况是入口退回底部。

功能

选择方式

  • 复选框列表(只列出真实对话,自动隐藏空白会话)
  • 按工作区分组:每个工作区一个标题(显示完整路径,路径过长时省略号出现在左侧、保留尾部目录名), 标题上带 已选/总数;点标题一次即可整组勾选/取消。分组顺序按该组内最新对话的时间排, 没记录工作目录的会话单独归到最后一组。工具栏的「按工作区分组」可以关掉(关掉后回到单纯按时间排序的 平铺列表),这个选择会记住
  • 全选 / 清空 / 反选
  • 搜索:按标题、工作目录、会话 ID 过滤
  • Shift + 点击按范围选择(范围按当前显示顺序,也就是分组后的顺序);Ctrl/Cmd + 点击追加选择
  • 已选计数实时显示

入口位置

入口按钮注入在会话列表工具栏那一行、搜索控件左侧,与原生控件同一行,间距与原生按钮一致。

这是整个插件唯一的 DOM 注入:那个工具栏由 dsh-client-ui-workspace 渲染,官方没有声明任何 header slot, 「放在放大镜左边」在官方扩展面上无法实现。做法是把按钮绝对定位在搜索控件左侧、脱离 flex 流:

  • 必须脱离 flex 流,这是两次踩坑换来的结论。该行是 display:flex; gap:4px; justify-content:flex-end, 其中搜索槽是 flex:1; max-width:28px; overflow:hidden —— 只为搜索按钮预算了 28px。 按钮一旦参与这一行的 flex 排布(无论插进控件内部,还是插成控件的兄弟节点), 都会把这 28px 挤爆,表现就是「放大镜被挤走 / 消失」。 现在 host 用 position:absolute 挂在控件左缘,不占这一行任何宽度,放大镜原地不动
  • 该行是带 overflow:hidden 的 flex item,会把绝对定位(露在盒外)的按钮裁掉,所以它的 overflow 用内联样式临时放开;卸载时连同 position:relative 和标记属性一起还原,不在别人的 DOM 里留痕迹
  • 按钮 28px,和原生 _searchButton 同尺寸,四个按钮共用一套节奏;间距不是硬编码的 4px,而是量出来的: 先测出这一行里「放大镜 → 下一个原生图标」的实际距离,再用「百分比定位 + 测量校正」的闭环把按钮放到同样距离。 transform 祖先会劫持绝对定位的包含块(纯百分比会算错),主题改了这一行的 gap 也能自动跟上
  • 用无障碍名称(搜索会话 / Search sessions)定位,而不是带构建哈希的类名 (另加 *_searchButton 与输入框 placeholder 两级兜底)
  • 点击有两路触发:React 的 onClick,外加一个挂在 document 上的捕获阶段 click 监听 (只认落在自己 host 里的点击)。按钮长在别人的 DOM、由本插件自己的 React root 渲染, 万一合成事件链路被别的 root 或外层处理器影响,捕获阶段的监听仍能拿到手势;重复打开是幂等的。 按钮与 host 还带 -webkit-app-region:no-drag:桌面壳若把窗口顶部做成拖拽区,拖拽区会吞掉点击
  • 面板开合状态放在插件自己的 store 里(不在某个组件的 state 里),所以会话列表重渲染那一行、 甚至重建 host,弹窗都不会跟着消失
  • MutationObserver 在会话列表重渲染后重新定位,复用同一个 host,不会重复插入
  • 入口只有头部按钮一个:侧边栏底部那个入口会显得多余(影响美观),因此它只在注入失败或定位不到目标时 才作为兜底出现;头部按钮可用时它不渲染。两者共用同一个面板;面板通过 portal 渲染到 body, 所以按钮位置与弹窗互不影响

批量操作

操作说明可靠性
删除永久删除选中的多个对话及其全部记录。点「删除」后按钮自身会亮起红框、确认框自动滚进视野,二次确认后才调用服务官方 session.delete 契约
Fork为每个选中对话各 fork 一份(标题自动递增)官方 session.fork 契约
导出 MD每个对话导出为一个 Markdown 文件(含元信息 + 全部用户/助手正文)读取会话历史后本地下载
导出 JSON结构化导出:sessionId / title / cwd / turns[]同上
汇总给 AI把多个对话正文拼成一份材料,新建一个对话并作为首条消息发出,随后自动打开该对话,让模型做逐条摘要 + 横向对比session.create + session.prompt + sessions.open
标为未读 / 已读在插件面板内标记并持久化,未读行显示圆点插件本地状态
置顶 / 取消置顶置顶的对话在面板内排到最前插件本地状态
归档 / 取消归档归档的对话默认从面板隐藏(勾选「显示归档」可见)插件本地状态

关于「归档」和「置顶」的重要说明

DSH 客户端 没有 host 侧的 archive / pin 契约(原生侧边栏的「未读」也只是工作区插件的 本地 localStorage 状态,未暴露成服务)。因此本插件的归档 / 置顶 / 未读是插件自己的持久化状态: 它们影响本多选面板的排序与显隐,不会改变原生侧边栏的显示。删除、Fork、导出、汇总走的是官方契约, 对所有界面都生效。

会话列表数据的形状

ctx.sessions.list.getSnapshot() 给的是归一化快照:{ ids, byId, current, phase, … } (工作区插件、dsh-better-sidebar 等都是在读 byId[sessionId])。同一服务上还存在一个拍平成显示顺序的 { items, … } 投影。本插件两种都认(snapshotItems()):只读 items 会让侧边栏满屏对话、 而面板里一条都列不出来。

byId 的值里没有 sessionId:控制器把 id 存成 id,标题存成派生出来的 displayTitle (title → cwd → id 依次兜底)。面板在 normalizeSummary() 里补齐这两个字段,并且跳过没有可用 id 的行 —— 否则每一行都会落在同一个 undefined 键上,表现就是「选一行 = 全选」,批量动作也会指向空 id。 测试夹具直接使用控制器的真实形状,就是为了让这类 bug 无法回流。

自己的翻译器

面板文案不依赖 slot 传进来的 t:那个 t 只在 footer 那个 slot 渲染之后才会存在,而应用可能根本不渲染它 (本机上就没有)。等它的结果就是满屏 panel.title、action.delete 这样的原始键。因此 bundle 自带 makeTranslator(),用 ctx.locale 的当前语言在自己的 zh/en 字典里取词,{name} 占位符自己插值。

面板崩了会说出来,而不是静默消失

面板包在一个 error boundary 里。没有它,React 在渲染报错时会卸载整棵树 —— 包括打开弹窗的那个按钮, 用户看到的就是「点了没反应」。现在弹窗会留着,并把失败原因显示成一行红字。

按工作区分组(以及它为什么改动了「顺序」)

分组不是单纯加个标题:它重排了行的显示顺序,而 Shift+点击的范围选择、全选/反选走的都是 「显示顺序」。所以 visibleRows() 先按置顶/时间排出基础顺序,groupRows() 只做稳定归组 (组内保持基础顺序,组按组内最新时间排,无工作目录的组排最后),面板再把 flattenGroups() 的结果当作唯一的顺序来源 —— 否则 Shift+点击会扫过用户在两击之间根本看不到的行。 行的 cwd 默认不再重复显示(标题已经写了),关掉分组后恢复显示。

诊断环(排查用)

插件在 sessionStorage 的 dsh.session.multiselect.diag 里维护一个最多 40 条的事件环: apply build=N / placed / click document / click react / panel open / panel rendered rows=N / panel error …,以及每个批量动作的结果:action empty / delete service … / delete start n=… / delete failed <id> <原因> / delete done ok=… failed=… / action error …。 它只写入本标签页的会话存储、写失败也绝不影响功能,用来回答「点击到底有没有到达按钮、 面板有没有渲染、删除到底报的什么错」这类只能靠现场才能判断的问题。

安装

插件是标准 DSH bundle 包(声明 dsh.bundle.patch + dsh.client),没有依赖需要安装:仓库里就是可直接加载的产物。安装后需要重启 DSH Desktop(profile 的 bundle 层栈与 __DSH_BOOT__ 客户端图在启动时合成)。

git clone https://github.com/xlxs123/dsh-session-multiselect.git
cd dsh-session-multiselect
dsh plugin --profile web add .     # 路径含空格时务必在包目录内执行 `add .`

或直接使用 pnpm 再重启:

# profile 目录:$DSH_HOME/profiles/web
<pnpm> add "link:<本包绝对路径>"
# 然后把 dsh-session-multiselect 追加到该 profile package.json 的 dsh.profile.bundles

注意:dsh plugin 在 Windows 上经 shell: true 转发参数,路径中的空格会被拆开。 因此请在包目录内执行 add .,不要把含空格的绝对路径当作参数传入。

卸载:dsh plugin --profile web remove dsh-session-multiselect(或从 profile 的 dsh.profile.bundles 里删掉这一项),重启后插件连同它的那一个 DOM 注入一起消失。

开发

lib/index.js      Node half(空实现:本插件没有 host 侧功能)
lib/client.js     浏览器 bundle:面板、选择模型、批量动作、头部注入(window.__ModuleLoader__ 契约)
src/index.ts      Node half 源码
tests/            56 个测试:纯逻辑单测 + 以假 Cordis 上下文挂载真实 bundle + 注入逻辑(含 DOM 桩)
tests/browser/    真实浏览器冒烟测试:真 React 18 + 无头 Chromium + 复制自工作区插件的头部 CSS
npm install                            # 只装 devDependencies(react/react-dom),供浏览器测试当 React 源
npm test                               # = node --test  (56 个文件级测试,约 0.4 秒)
npm run test:browser                   # 真实浏览器冒烟:需要 Chrome/Edge(自动探测路径)
npm run diag                           # 从桌面应用的 Session Storage 里读出诊断环

tests/browser/harness.mjs 会起一个静态服务(插件目录 + 真实 React UMD),用 CDP 驱动无头 Chromium 打开 click.html,断言真实布局下的结果:按钮落在哪、中心点上压着谁、真实点击能否打开面板、 间距是否等于原生按钮间距、分组标题是否显示完整路径、点标题能否整组勾选。Node 桩答不了的就是这类问题。 React UMD 的查找顺序是:SMOKE_APP_NODE_MODULES / DSH_APP_NODE_MODULES → 本包 node_modules → DSH Desktop 安装目录(Windows %LOCALAPPDATA%\Programs\...、macOS /Applications/DSH Desktop.app/...)。 Node 只要 20+:CDP 客户端优先用 Node 自带的 WebSocket(v22 起才有),否则用 devDependency ws (CI 跑在 Node 20 上,走的就是这条路;SMOKE_WS=ws 可以强制走它做验证)。 可覆盖的环境变量:SMOKE_BROWSER、SMOKE_PORT、SMOKE_CDP_PORT、SMOKE_APP_NODE_MODULES、 SMOKE_KEEP_BROWSER=1。CI(GitHub Actions)跑的就是这两条命令。

lib/client.js 是手写的 CJS 形式浏览器 bundle(与官方客户端插件同构),无需构建步骤即可被 客户端模块系统加载;tests/ 通过伪造 window.__ModuleLoader__ 与 require 直接驱动这份产物, 所以测试覆盖的就是实际发布的代码 —— 插件作者改一行 lib/client.js,测试跑的就是那一行。

已知限制

  • 不能把复选框放进原生会话行:原生列表没有逐行 slot(见上)。本插件提供独立面板作为替代方案。
  • 入口按钮是 DOM 注入:官方面没有 header slot,所以「放大镜左侧」只能靠注入实现。 DSH 若改动该工具栏结构(改了无障碍名称又换了类名),注入会失败 —— 届时按钮自动退到侧边栏底部 (sidebar.footer.action,设置图标左侧),不会消失、也不影响功能。注入还会给工具栏那一行加一个 限定作用域的 overflow 覆盖(否则绝对定位的按钮会被 overflow:hidden 裁掉),卸载时该覆盖会被移除。
  • 归档 / 置顶 / 未读是插件本地状态:DSH 客户端没有 host 侧的 archive / pin 契约(原生侧边栏的 「未读」也只是工作区插件的 localStorage 状态),所以这三项只影响本面板的排序与显隐。
  • 汇总有长度上限:为控制上下文体积,汇总材料默认上限 120000 字符;超出时按整段截断, 并在提示词与界面状态里明确标注被截断,不会让模型误以为看到了全部内容。
  • 长会话读取有页数上限:每个对话最多向上翻 20 页(每页 200 条消息),避免超长日志卡住面板。
  • 删除不可撤销:批量删除走官方永久删除契约,这是设计如此,确认框会明确写出数量。
  • 只在 Web 客户端上做过实测:插件声明 platform: "web";Tauri/其他壳未验证(理论上同构, 但 -webkit-app-region 之类的桌面壳差异没有实测数据)。

许可

MIT,见 LICENSE。

Comments

Loading…

Similar plugins

dsh-archive-session

by DreamStan

A DSH plugin for browsing archived sessions and safely deleting them.浏览归档会话,并安全地关闭、取消归档或删除会话。

Manifest valid

★ 0

MIT

TypeScript

Aug 18, 2026

dsh plugin --profile web add @dsh-external/dsh-archive-session

by ailiasdesu

DSH session migration plugin: drag-and-drop sessions between workspaces in the Web UI settings panel (auto backup/rollback, official workspaceRegistry sync)

Terminal & ClientsManifest valid

★ 0

BSD-3-Clause

JavaScript

Aug 28, 2026

dsh plugin --profile web add @dsh-external/dsh-session-manager

by Ryuu-64

DSH web plugin: a Cmd/Ctrl+F find bar for the DeepSeek Harness desktop shell that searches the WHOLE conversation, not just the rendered tail.

Development & InfrastructureUI & ExperienceTools & CapabilitiesManifest valid

★ 0

MIT

JavaScript

Sep 26, 2026

dsh plugin --profile web add dsh-find-all

Share a selected chat segment as Markdown, HTML, TXT, or PNG; Session-header dialog with range or multi-select, redaction, tool calls, and subagent conversations, plus the /share command.

Sessions & MessagesTerminal & ClientsManifest valid

★ 0

dsh plugin --profile web add dsh-session-chat-share

by SZMY-haruhi

Sidebar archive directory for DSH Web: restore a session to its original workspace slot, delete the session log, or edit-and-branch a user message into a new conversation.

Sessions & MessagesDevelopment & InfrastructureManifest valid

★ 2

↓ 103/wk

MIT

TypeScript

Aug 17, 2026

dsh plugin --profile web add dsh-session-plus

by NIGHT576

Real deletion for DSH Web conversations: a session-row menu action and a Workspaces-header batch dialog; removes each session's log directory and projection cache. 给 DSH Web 侧栏补上真正的会话删除:右键菜单删单条、区头批量删多

Manifest valid

★ 0

MIT

JavaScript

Sep 12, 2026

dsh plugin --profile web add dsh-delete-session