🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

会话历史面板 wangxiang0605qvq/dsh-session-history

DeepSeek 客户端
wangxiang0605qvq35 天前有提交已提供中文文档扫描:无法判定兼容 / 相关生态

浏览归档历史会话,彻底删除已归档日志

29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/1 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/wangxiang0605qvq/dsh-session-history.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 35 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
⚪ 扫描:无法判定尚无人工评估
展开查看扫描说明与证据 ↓

已尝试扫描但取不到可分析的源码:仓库或包不存在、已删除,或为非 JS/TS 实现、未随包发布源码。无法判定,不代表安全。

  • · 取源不可用(Error: github tarball HTTP 404 @ https:
  • · api.github.com
  • · repos
  • · wangxiang0605qvq
  • · dsh-session-history
  • · tarball)
扫描于 2026/11/2(1 天前)

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 侧边栏历史会话面板
  • 按工作区分组搜索
  • 彻底删除归档日志

DSH 会话历史插件:侧边栏历史会话面板,按工作区分组、搜索、归档或恢复,还能把已归档会话从磁盘彻底删除 | DSH 会话历史面板,带保护机制,可永久删除已归档的会话日志

README

由 DeepSeek 最新模型翻译生成
@deepseek-ai/dsh-session-history DSH 桌面端的会话历史面板:浏览历史会话、打开、归档(单个 / 批量)、恢复,以及把已归档的会话从磁盘彻底删除。 A session history panel for the DSH desktop app: browse, open, archive (singly or in bulk), restore, and permanently delete an archived session's log from disk. 两种“删除”,含义完全不同 | | 归档(Archive) | 彻底删除(Purge) | | 谁提供 | DSH 自带:workspaceRegistry.archiveSession | 本插件:Host 半边直接删文件 | | 效果 | 离开列表,JSONL 日志与工作区记账保留 | 从磁盘上删掉该会话的日志目录 | | 可恢复 | 可以,unarchiveSession | 不可以 | | 用在 | 列表里任何会话 | 只有已归档的会话 |
展开完整文档(README 剩余 1 节)
@deepseek-ai/dsh-session-history

DSH 桌面端的会话历史面板:浏览历史会话、打开、归档(单个 / 批量)、恢复,以及把已归档的会话从磁盘彻底删除。

A session history panel for the DSH desktop app: browse, open, archive (singly or in bulk), restore, and permanently delete an archived session's log from disk.

两种“删除”,含义完全不同

| | 归档(Archive) | 彻底删除(Purge) |
| 谁提供 | DSH 自带:workspaceRegistry.archiveSession | 本插件:Host 半边直接删文件 |
| 效果 | 离开列表,JSONL 日志与工作区记账保留 | 从磁盘上删掉该会话的日志目录 |
| 可恢复 | 可以,unarchiveSession | 不可以 |
| 用在 | 列表里任何会话 | 只有已归档的会话 |

DSH 本身没有会话硬删除:workspaceRegistry 只有 archiveSession/unarchiveSession/pinSession/unpinSession(唯一的 delete(id) 删的是整个工作区注册,而且它明说 “retains the directory and every session log”);sessionPersistence 只有 create/open/flush/stat/list;发行版自己的会话 ... 菜单也只注册了 pin/rename/fork/archive。

所以这一版按你的要求把彻底删除补上了 —— 走本插件自己的私有 RPC 通道 + Host 半边直接删文件,不假装 Host 有这个能力。

长什么样

侧边栏新增一个面板图标(时钟表盘),点开是主列的面板:

┌─────────────────────────────────────────────────────────────────┐
│ 会话历史 [搜索…] [刷新] [全部归档] │ │ 共 12 条,已归档 3 条 │ ├─────────────────────────────────────────────────────────────────┤ │ 默认工作区 C:\…\default-workspace 5 │ │ ● DeepSeek Harness 实时显示插件 2 分钟前 │ │ ○ DeepSeek 插件:时钟 3 天前 │ │ │ │ 其他目录 1 │ │ ○ 改名过的会话 1 周前 │ │ │ │ 已归档 3 [全部恢复]│ │ ○ 旧的实验 2 周前 ↺ 🗑 │ │ ↑ 点 🗑 后原地变成 [🗑 确认删除] [取消],8 秒无操作自动收起│ ├─────────────────────────────────────────────────────────────────┤ │ 移除会话 = 归档(日志保留、可恢复);已归档的会话可以再「彻底删除」│ └─────────────────────────────────────────────────────────────────┘

- 分组:按会话 cwd 匹配工作区路径分组,匹配不上进「其他目录」;组内按最近活动倒序。
- 绿点:该会话正在运行。
- 搜索:按标题或目录路径过滤。
- 行悬停:→ 打开、🗄 归档;已归档行是 ↺ 恢复 + 🗑 彻底删除。
- 二次确认:「全部归档」和「彻底删除」都要点两次,8 秒无操作自动取消。归档一个还在跑工作的会话也要确认一次(见下节)。
- 归档语义:先不带 stopActivity 地问一次,只有 Host 用 workspace/session-active 拒绝时,面板顶部才出现确认条,把 Host 报的运行内容列出来,等你点「停止并归档」再带 stopActivity: true 重试。不拿 running 猜。

为什么归档要先问一次,而不是先看 running

SessionSummary.running 只是 ctx.remote.session.list() 的轮询快照,而“这个会话现在能不能归档”由 Host 侧的 Agent 注册表当场裁决,两者会不一致。按快照猜的写法一旦猜错(快照说没在跑、Host 说在跑),archiveSession 就会被 workspace/session-active 拒绝,而那时插件把任何拒绝都当“失败”上报 —— 结果就是正在使用的会话永远归档不掉,界面上没有任何出路。这正是修掉的那个 bug。

现在改成和发行版侧边栏完全相同的形状:

archiveSession(id) ← 先不带 stopActivity 问一次
├─ 成功 → 「已归档 N 条会话」 └─ workspace/session-active → 面板顶部确认条:列出 Host 报的运行内容(turn / job / subagent / schedule) [停止并归档] → archiveSession(id, { stopActivity: true })text [取消] → 收起(15 秒无操作自动收起)

判定拒绝用的是 RPC code(error.rpcError.code === "workspace/session-active")而不是 instanceof:workspaces 服务住在另一个 client bundle 里,插件 bundle 之间不共享 error class 身份。发行版自己的 activeSessionRefusal() 按 error.name 判,原因相同。

附带的好处:以前对 running === true 的行直接传 stopActivity,等于不问就取消对方正在跑的轮次、并丢弃排队中的输入;现在这一步必须先经过你确认。

其它错误(例如 session/not-found)仍然直接报错,并且从这一版起报错文案会带上 RPC code,例如 操作失败:session/not-found: …,不用再猜是哪种失败。

彻底删除是怎么做的(以及为什么这样写)

磁盘布局(在本机 0.2.0-rc.2 profile 上实测确认): text /sessions///session.v4.jsonl.zstd

sessionId 实际取值都在 [A-Za-z0-9._-] 内(session- 与裸 两种形式都出现过),所以 encodeSegment 不会改动它 —— 目录名就是 id 本身。

lib/index.js(Host 半边)因此不复刻后端的 projectKey 编码,而是直接按目录名精确匹配。所有守卫都朝“宁可拒绝,绝不删错”的方向排:

| # | 守卫 | 挡住的情况 |
| 1 | id 必须是单个普通路径段(/^[A-Za-z0-9._-]+$/,且不是 . / ..) | 路径穿越 |
| 2 | 调用方必须显式传 confirm: true | 误调用 |
| 3 | agents.get(id) 有值就拒绝 | 会话还活着 —— 删掉正在读写的日志会毁掉运行中的会话 |
| 4 | sessionPersistence.stat(id) 必须仍认得这个会话 | 拼错 / 过期的 id 撞上同名目录 |
| 5 | 只按目录名精确匹配,且必须恰好一个匹配 | 同名出现在多个 project 目录时拒绝猜 |
| 6 | 目录里必须真的有 .jsonl* 日志产物 | 删到不像会话目录的东西 |
| 7 | rm 带 maxRetries | Windows 上读者刚关句柄的 EBUSY 抖动 |

守卫 3 有个反直觉的后果:归档 ≠ 卸载

agents.get(id) 查的是活着的 Agent。而一个会话只要还被窗口打开着,它的 Agent 就还活着 —— 归档只是把它从列表里移走,不卸载它。所以在列表里“删掉”了某个会话、再去点 🗑,会遇到守卫 3,看起来像“我都删了怎么还说它开着”。

这不是 bug,是守卫在挡“从正在读写的日志底下把文件抽走”。两条出路:

1. 先离开这个会话 —— 切到别的会话,或新建一个;等它的 Agent 卸载后再回来删。
2. 重启 DSH,然后不要打开它 —— 直接从本面板的「已归档」分组里删。重启后只有 DSH 自己打开的会话是活的,归档过的那个一定是死的,这条最稳。

面板现在按 RPC code(session-loaded)把这种拒绝本地化成上面那段可执行的说明,而不是原来那句英文 close it before deleting its log —— 后者根本没法照做,因为 DSH 没有“关闭会话”这个按钮。

删完返回 { sessionId, directory, project, files, bytes },面板把它汇总成「已彻底删除 N 条会话,释放 X MB」。多条时是串行的。

投影缓存不删:session-projection-cache 里按键为 session id 的条目在会话消失后是惰性的(几十 KB),为了它去动共享 storage domain 风险远大于收益。

数据来源

| 用途 | 来源 |
| 会话列表(标题 / 时间 / cwd / running / blank) | ctx.remote.session.list({}) → SessionSummary[] |
| 标题文本 | summary.projections.values.title.title(title 投影单元发布的是对象) |
| 分组 / 归档集 / 置顶集 | useWorkspaces((s) => s.items / s.archivedSessionIds / s.pinnedSessionIds) |
| 归档 / 恢复 | ctx.workspaces.archiveSession(sessionId)(被拒时经确认再带 { stopActivity: true } 重试)/ unarchiveSession(sessionId) |
| 归档被拒的原因 | WorkspaceArchiveError.rpcError.code === "workspace/session-active",.details.activity 是 SessionActivity[](turn / job / subagent / schedule) |
| 打开会话 | ctx.uiWorkspace.openSession(sessionId) |
| 彻底删除 | 私有 RPC 通道 /session-history 的 delete 端点 → 本插件 Host 半边 |

槽位与「不用写导航」

sidebar.panellist 的契约原文:

Global panel icons. Each list id addresses the matching main panel; the sidebar owns the button and resolves its label from list metadata.
只要 sidebar.panellist 的 id 与 main 的 key 都是同一个 PANEL_ID("session-history"),侧边栏自己渲染按钮、管选中态、切面板 —— 插件里没有一行导航代码。main 的注册用 generator 形式,和发行版唯一的 main 注册(Plugins 页)一致。

私有路由契约(两端都由本插件拥有,因此没有套 Connection 的 RPC 信封 {type,rpcId,result},就是一个普通的 {ok, value|error} JSON): js // Host(webServer 的 prefix 路由,handler 是 node 风格)
ctx.inject(["webServer", "connection"], (scoped) => {
scoped.effect(() => scoped.webServer.register({ kind: "prefix", path: "/session-history", handler }), label);
});
// Browser(同源 POST,浏览器会话 cookie 随请求带上,正是 Host 准入判据所查的) await fetch("session-history/delete", { method: "POST", headers: { "content-type": "application/json" }, body: … }); // → { ok: true, value } | { ok: false, error: { code, message, details } }

为什么不用 connection.rpc.handle:它的实现是 owner.effect(() => owner.webServer.register(route)),owner 取的是 connection 服务自己的 context,注册时机不由本插件掌握。发行版里 client-modules、client-file-upload、client-hmr 三个插件都是直接挂 webServer,本插件跟随这条更可控的路径。

文件

| 文件 | 作用 |
| lib/client.js | 浏览器半边:会话 store、面板 UI、行组件、内联 SVG 图标、两个槽位注册 |
| lib/index.js | Host 半边:私有 RPC 通道 + 带守卫的磁盘删除 |
| package.json | 双面包声明 exports["./client"] + dsh.client.platform = "web" + dsh.bundle.patch |
| cordis.patch.yml | bundle 层:声明本包自带的挂载行,作为 bundle 安装时由插件管理器读取 |

安装

方式一:插件管理器(推荐)

本仓库通过 dsh.bundle.patch 声明了自带的 cordis.patch.yml,因此不需要手写补丁行: powershell
dsh plugin --profile desktop add github:wangxiang0605qvq/dsh-session-history

方式二:手动放进解析根(离线可用)

要点:包必须放在 $DSH_HOME\profiles\node_modules\ 下 —— 那是运行时模块解析的查找根。放进 profiles\desktop\node_modules\ 不会被解析到,只写补丁行同样找不到模块。
powershell
$dest = "$env:USERPROFILE\.dsh\profiles\node_modules\@deepseek-ai\dsh-session-history"
New-Item -ItemType Directory -Force -Path (Split-Path $dest) | Out-Null
Copy-Item -Recurse -Force . $dest
Remove-Item "$dest\.git" -Recurse -Force -ErrorAction SilentlyContinue

然后在 ~/.dsh/profiles/desktop/cordis.patch.yml 末尾追加: yaml
- insert:
- id: session-history
name: "@deepseek-ai/dsh-session-history"
config: {}

重启 DSH,然后硬刷新(Ctrl+F5)页面。

方式三:作为 profile 本地插件(本机实测通过的路径)

不需要动 node_modules,把 lib/ 下的两个文件当作插件的两个半边即可:

~/.dsh/profiles/desktop/plugins/session-history/
├── package.json # 声明 exports["./client"] 与 dsh.client.platform = "web" ├── index.js # 即 lib/index.js └── client.js # 即 lib/client.js

补丁行用相对路径,由 patch 文件所在目录解析: yaml
- insert:
- id: session-history
name: ./plugins/session-history/index.js

注意:window.__ModuleLoader__.load({ id }) 必须与 package.json 的 name 完全一致,否则 browser 模块表找不到对应的 factory,插件会静默不生效。本仓库两处都是 @deepseek-ai/dsh-session-history;若你把 name 改成别的值,lib/client.js 里的 load({ id }) 要同步改。

⚠️ 改了 lib/index.js 必须重启 DSH

Host 半边不会热重载。 profile 的 HMR 只重新投递 client 半边(client-modules 按文件 mtime 生成带 revision 的 bundle URL,页面重新拉取),Host 半边是进程内 ESM,同一个 URL 在进程存活期间只求值一次 —— 插件管理器文档也写明 "Package replacements require restarting the process to load a fresh JavaScript module generation"。

症状就是第一次踩到的那个:改了 lib/index.js(哪怕改成完全空的 apply),进程里跑的还是启动时那份,路由根本不注册,客户端 POST 穿透到静态 SPA 回退,报 transport failure for /session-history/delete: HTTP 405。改 lib/client.js 不需要重启。

判断当前进程加载的是哪一份:比对 lib/index.js 的 LastWriteTime 与 DSH 进程的 StartTime(Get-Process -Name "DeepSeek Harness")。前者晚于后者,就说明改动还没生效。

排查

HTTP 405 / transport failure —— 路由没注册,请求落到了静态前端回退(dsh-host-frontend-static 的 fallback 座位只接受 GET/HEAD,其它方法一律 405)。按顺序检查:

1. lib/index.js 的 mtime 是否晚于 DSH 进程启动时间?是 → 重启 DSH。
2. 重启后仍 405 → 用未认证探针区分两种情况:

powershell
Invoke-WebRequest "http://127.0.0.1:19387/session-history/delete" -Method POST
-ContentType "application/json" -Body '{"sessionId":"x","confirm":true}'

- 405 = 路由仍未注册(ctx.inject(["webServer","connection"]) 没满足)
- 401 / 403 = 路由已注册,被 auth 判据挡住了(浏览器里带 cookie 就不会)

彻底删除被拒 —— 路由通了,是守卫拒的。Host 现在给每种拒绝一个 code,面板按 code 本地化;没有 code 的走通用 purge-refused:

| code | 含义 | 怎么办 |
| session-loaded | 会话还活着(Agent 没卸载) | 先离开这个会话,或重启 DSH 后不打开它 —— 见「守卫 3 有个反直觉的后果」 |
| session-unknown | Host 已经不认识这个会话 | 刷新列表;它可能已被删掉或移走 |
| ambiguous-session | 同名目录出现在多个 project 目录 | 插件拒绝猜,需要手动确认 |
| no-log-artifact | 目录里没有 .jsonl 日志产物 | 不像会话目录,插件拒绝删 |
| bad-session-id / confirm-required | 调用参数不合法 | 正常 UI 不会触发 |

可调项

| 想改什么 | 改哪里 |
| 侧边栏图标位置 | lib/client.js 注册处的 order: 50 |
| 面板 ID / RPC 通道名 | PANEL_ID、CHANNEL(两端必须同时改,client 与 index 各有一处) |
| 会话根目录 | lib/index.js 的 sessionsRoot()(默认 $DSH_HOME/sessions,回退 ~/.dsh/sessions) |
| 二次确认宽限时间 | 「全部归档」与「彻底删除」两处 setTimeout(..., 8000) |
| 归档确认条自动收起时间 | 面板里那个 setTimeout(() => actions.cancelStop(), 15000) |
| 归档要不要问(而不是直接停) | applyBatch 只在不带 stopActivity 时才把 workspace/session-active 转成确认条 |

已知边界

- 彻底删除不可恢复;面板只对已归档的行提供它,且需要二次确认。
- 正在使用的会话也能归档,但要先确认停止它正在跑的工作:那一停会取消当前轮次并丢弃排队中的输入,所以插件不会替你做这个决定。
- 已归档但还开着的会话删不掉日志(守卫 3):归档不卸载 Agent。先离开它,或重启 DSH 后不打开它再删。
- 日志根目录按 $DSH_HOME/sessions 推导。推错不会删错:守卫 5/6 会让操作直接拒绝。
- 投影缓存里的对应条目会留下(惰性,不影响功能)。
- 面板依赖 ctx.remote.session.list() 是否包含已归档会话;若某版本把归档会话排除在外,「已归档」分组会少掉那些行(恢复本身仍通过 archivedSessionIds 生效)。
- 子会话(origin === "subagent" 或有 parentSessionId)不列在历史里。
- 无头 / TUI 环境没有侧边栏与主列,面板不显示。

License

MIT
预览分享卡片长什么样 ↓wangxiang0605qvq/dsh-session-history
分享 · DeepSeek Harness Hub
wangxiang0605qvq/dsh-session-history

浏览归档历史会话,彻底删除已归档日志

这是你的仓库?认领条目

回到顶部