在侧边栏菜单中彻底删除对话及其磁盘日志
30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/16 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add nixuejun888/dsh-plugin-session-delete该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · browser
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 18 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(node_version · 2026/10/31) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 14 处,展示前 14 条)
- 读取本地文件lib/index.js:134
return (await stat(path)).isDirectory() - 读取本地文件lib/index.js:142
const entries = await readdir(path, { withFileTypes: true }) - 读取本地文件lib/index.js:173
for (const entry of await readdir(path, { withFileTypes: true }).catch(() => [])) { - 读取本地文件lib/index.js:175
total += await stat(join(path, entry.name)).then((info) => info.size, () => 0) - 读取本地文件lib/index.js:187
const info = await stat(dir).catch(() => null) - 写入本地文件lib/index.js:203
await mkdir(pluginRoot(), { recursive: true }) - 写入本地文件lib/index.js:204
await appendFile(auditLog(), `${JSON.stringify(entry)}\n`, 'utf8') - 删除本地文件递归lib/index.js:398
await rm(dir, { recursive: true, force: true }) - 写入本地文件lib/index.js:402
await mkdir(trashRoot(), { recursive: true }) - 写入本地文件lib/index.js:406
await rename(dir, target) - 写入本地文件lib/index.js:418
await rename(item.to, item.from) - 读取本地文件lib/index.js:461
const hadCache = await stat(cacheFile).then(() => true, () => false) - 删除本地文件lib/index.js:465
await rm(cacheFile, { force: true }) - 发起网络请求lib/client.js:317
const response = await window.fetch(API + '/delete', {
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
真正删除 DSH 对话:侧边栏会话菜单里加「删除对话」项,并额外删除会话日志和投影缓存。
README
由 DeepSeek 最新模型翻译生成dsh-plugin-session-delete
给 DSH 加一个「删除对话」能力:会话行的 ... 菜单里多一项删除对话,确认后真正把该对话从磁盘上移除。
English — A Delete conversation action for DSH (DeepSeek Harness). DSH ships no session-deletion API (dsh-session-persistence is append-only; @deepseek-ai/dsh-workspace can only archive), so this plugin deletes out-of-band: it archives the session first — that is what makes the sidebar row disappear immediately — then moves the session log and its projection cache into a recoverable trash directory, or permanently removes them when the「永久删除」box is ticked. Install by cloning this repository and linking it into yo
展开完整文档(README 剩余 1 节)
dsh-plugin-session-delete
给 DSH 加一个「删除对话」能力:会话行的 ... 菜单里多一项删除对话,确认后真正把该对话从磁盘上移除。
English — A Delete conversation action for DSH (DeepSeek Harness). DSH ships no session-deletion API (dsh-session-persistence is append-only; @deepseek-ai/dsh-workspace can only archive), so this plugin deletes out-of-band: it archives the session first — that is what makes the sidebar row disappear immediately — then moves the session log and its projection cache into a recoverable trash directory, or permanently removes them when the「永久删除」box is ticked. Install by cloning this repository and linking it into your DSH profile (see 安装 below); it is not published to npm. MIT licensed. Both halves load only at DSH startup, so a full restart is required after installing or editing it.
为什么需要它
DSH 本身没有删除会话的 API:
- dsh-session-persistence 是只追加的(create/open/stat/list/flush),README 明确写着 "No deletion or retention API";
- @deepseek-ai/dsh-workspace 的 archiveSession 只是隐藏(归档)会话,README 明确写着 "session deletion or folder removal are separate, absent capabilities"。
所以删除只能做带外维护,直接操作持久层拥有的存储布局:
<DSH_HOME>/sessions/<项目slug>/<sessionId>/session.v4.jsonl.zstd ← 会话日志 <DSH_HOME>/storages/session_projcache/sessions/<sessionId>.json ← 投影缓存
安装(本地挂载,未发布 npm)
包名是 @local/dsh-plugin-session-delete:@local/ 作用域表示这是本地插件,没有发布到 npm(private: true 就是防止误发),所以只能下载发布包或克隆仓库后 link: 挂载。下面用 <REPO> 表示插件目录、<DSH_HOME> 表示 DSH 主目录(默认 ~/.dsh)、<profile> 表示 profile 名(如 desktop)。
方式 A:下载发布包
每个版本 tag 都会由 release.yml 打出一个可直接安装的 zip(内容 = lib/ + package.json + cordis.patch.yml + README.md + LICENSE):
1. 从 Releases 下载 dsh-plugin-session-delete-vX.Y.Z.zip,用同一个 release 里的 SHA256SUMS.txt 校验后解压;
2. 把解压出的 dsh-plugin-session-delete/ 整个目录放进 <DSH_HOME>/local-plugins/,于是 <REPO> = <DSH_HOME>/local-plugins/dsh-plugin-session-delete;
3. 继续下面的「挂载」。
方式 B:克隆仓库
git clone https://github.com/nixuejun888/dsh-plugin-session-delete <REPO>
挂载(两种方式相同)
1. <DSH_HOME>/profiles/<profile>/package.json 的 dependencies 里加 "@local/dsh-plugin-session-delete": "link:<REPO>";
2. 在 <DSH_HOME>/profiles/<profile>/node_modules/@local/ 下建一个指向 <REPO> 的链接:
- Windows:New-Item -ItemType Junction -Path <...>\node_modules\@local\dsh-plugin-session-delete -Target <REPO>
- macOS / Linux:ln -s <REPO> <...>/node_modules/@local/dsh-plugin-session-delete
3. <DSH_HOME>/profiles/<profile>/cordis.patch.yml 末尾加
- insert:
- id: session-delete
name: '@local/dsh-plugin-session-delete'
4. 完全重启 DSH(两半都只在启动时加载,见下文「生命周期」)。
卸载:删掉上面三处,重启 DSH。回收站里的会话目录可以手动移回 sessions/<项目slug>/。
仓库结构
lib/index.js Host 半边:HTTP API + Agent 工具 + 带外删除 lib/client.js Client 半边:手写的 window.__ModuleLoader__ bundle(无构建步骤) test/host.test.mjs Host 单元测试(假 DSH_HOME,安全) test/client.test.mjs Client bundle 冒烟测试(stub 出 React/window/document) test/e2e-real.mjs 真实存储上的可逆端到端(默认不跑,见下) cordis.patch.yml 供 profile bundle 方式挂载
Host 半边(lib/index.js)
- inject = ['tools', 'webServer', 'connection']。
- HTTP API 前缀 /dsh-session-delete-api(走 ctx.connection.admit(req) 鉴权;取不到鉴权服务时直接 503 拒绝,不降级放行):
- GET /status → 存储根目录、回收站目录、磁盘上会话数量;
- GET /sessions → 磁盘上所有会话(id / 项目 / 路径 / 大小 / 修改时间);
- POST /delete { sessionId, mode } → 删除。
- 工具 dsh_delete_conversation({ sessionId, mode? }),让 Agent 也能删。
- 安全护栏:非法 id、当前正在进行的会话、仍在运行的会话一律拒绝。判定「仍在运行」用的是 DSH 自己的准入缝隙 ctx.waterfall('workspace/session-activity', { sessionId }, () => []) —— 与 dsh-workspace 归档会话时同一个 waterfall(由 Agent registry / job registry / Subagent runtime / Schedule 服务商回答);没有 waterfall 的运行时降级为查 ctx.agents。不要用 sessions.list() / sessionQuery.listSessions() 判断 live:它们返回的是所有已持久化的会话,会把每个对话都判成 live(这正是 0.1.0 版「任何对话都删不掉」的原因)。
- 默认 mode = trash:把会话目录 rename 到 <DSH_HOME>/plugins/dsh-session-delete/trash/<id>--<时间戳>,可人工恢复;mode: 'purge' 才是 rm -rf。默认值取自插件配置的 defaultMode。审计记录写在 <DSH_HOME>/plugins/dsh-session-delete/deleted.jsonl:成功、失败、部分失败都追加一条(失败条目带 ok:false、restored、destroyed、stranded)。
- 回收站没有自动清理策略,会一直占用磁盘(也会被 DSH_HOME 的备份一并带走)。需要回收空间时自行清空该目录,或用界面上的「永久删除」。
- 同一个 id 若在多个项目 slug 下都留有目录,会全部移除(每个副本一个 trash 条目,名字追加 -1、-2…),避免删一半、留一半。
- projectionRemoved 表示「投影缓存原本存在且已删除」,不存在时为 false(rm(..., {force:true}) 对不存在的文件也会成功,所以先 stat 再断言)。
- 失败回滚是有损的:purge 已经删掉的目录无法恢复,只能如实报告 destroyed(已永久删除的目录)与 stranded(还原失败、滞留在回收站的副本)。只有确实什么都没丢(destroyed 与 stranded 都为空)时才把行放回侧边栏;否则保持归档、报 io-error,因为放回去只会留下一个打不开的行。
- 先归档再动文件:删除前调用 workspaceRegistry.archiveSession(id)(必须在文件还在时调用,注册表拒绝未知 id)。归档是「让这一行当场消失」的唯一受支持手段,见下一节。
- 注册表不存在(archiveSession 不是函数)、或它不认识这个会话(WorkspaceUnknownSessionError)时,照常删除:前一种运行时根本没有归档集可用,后一种说明侧边栏也没有它的行。
- 注册表拒绝归档时,先看拒绝原因是不是「有活动」:是(WorkspaceActiveSessionError 自带活动清单,或紧接着重查 waterfall 仍有活动)就报 reason: 'active' 并拒绝删除;其它原因的拒绝不否决删除,只把 archiveError 一起返回、archived: false,message 里说明「侧边栏可能仍显示这个对话:重启 DSH 后会消失」。理由:归档准入会调用全库的 sessionPersistence.list(),任何一条日志损坏都会让每个 id 都抛错;让装饰性的一行否决删除,就等于复现「任何对话都删不掉」。
- 回滚只撤销本次调用自己建立的归档(先读 registry.archivedSessionIds;读不到时视为「不确定」,该次归档不参与回滚),不会把别处预先存在的归档状态抹掉。
- 并发:同一个 sessionId 的删除用 in-flight 集合串行化,第二个调用者得到 reason: 'busy',不会和第一个抢同一批文件。
- 拒绝原因:invalid-id、invalid-mode、current-session、busy、not-found、active(有活动在跑)、io-error。其中 current-session 要在任何磁盘查找之前用 DSH_SESSION_ID 判定(否则当前会话目录已不在时,报的是 not-found)。注意它的范围是本进程自己正在进行的那个会话;「已经打开但空闲」的对话是故意允许删除的 —— 用户要删的正是它。
- 不修改 workspace.json:会话成员关系是「读时过滤」,宿主下次写入时会自行剪枝;归档集就是在这里落盘。
- 生命周期:注册走 ctx.effect(() => registerSurfaces(...), label)。Cordis 会忽略插件 apply 的返回值,所以「return 一个清理函数」等于从不清理 —— 卸载后会留下一个活着的工具和一条 500 inactive context 的僵尸路由,直到重启。
- 改完 host 半边必须重启 DSH:DSH 不会热重载插件模块,仅改 cordis.patch.yml 也无法让新代码生效(而且拿掉 insert 条目再放回去会把该 fiber 置于半加载状态)。
Client 半边(lib/client.js)
手写的 window.__ModuleLoader__.load({ id, factory }) bundle(无构建步骤,只用 shell 预置的 require('react')):
- 往 sidebar.workspaces.session.menu.item 注册 id: 'session-delete'(order: 500),props 为 { sessionId, displayTitle };
- 往 shell.overlay 注册确认对话框与结果提示;对话框里可勾选永久删除(不进回收站),勾选后请求带 mode: 'purge',按钮变成红色的「永久删除」;
- 确认后的顺序是先藏行、再删文件:先用客户端 workspaces.archiveSession(sessionId) 把这一行收起来,再 POST /delete;宿主拒绝或请求失败时用 unarchiveSession 把行放回去。归档必须排在请求之前 —— 注册表只接受「仍然存在」的会话,等宿主应答时日志已经不在磁盘上了。对当前版本的 Host 这一步是 no-op(Host 已经归档过),它的价值在于 Host 还是旧代码、或宿主归档被拒时仍能让这一行离开侧边栏;
- 成功后若 900ms 时侧边栏仍在渲染 [data-row-key="session:<id>"] / [data-session-id="<id>"],只把「侧边栏还在显示它:重启 DSH 后这一行会消失」追加进提示里 —— 绝不自动 location.reload():行还在是因为该会话常驻在 host 内存里,刷新页面会向同一个宿主重新取到同一份列表,既无效,又会丢掉用户正在输入的草稿;
- 对话框用的是应用主题令牌(--dsw-alias-bg-overlay / --dsw-alias-label-primary / --dsw-alias-border-l1 / --dsw-alias-state-error-primary),不是硬编码深色;结果提示是整屏点击穿透的遮罩,不会吃掉屏幕中央的点击;
- 已知边缘情况:若侧边栏的归档筛选被切到「显示已归档」,归档会照常保留这一行(sessionVisible 的 case "show": return true),任何提示也去不掉它。把筛选切回默认档即可。
- Client 半边同样要重启 DSH:client bundle 在 DSH 启动构图时被 readFileSync 读入并按 immutable 缓存,刷新页面不会重新取包。
测试
三个可重复运行的套件,都在 test/,用 Node 22+ 执行,不需要安装任何依赖: bash
npm test # host + client 两套,安全:host 测试只用临时目录
npm run test:e2e # 真实存储上的可逆端到端(默认拒绝运行,见下)
| 脚本 | 覆盖 | 结果 |
| test/host.test.mjs | Host 半边:全部拒绝原因、trash/purge、多 slug、并发 busy、归档四态(含「非活动拒绝仍删」)、部分失败与回滚(withLocked 制造确定性存储失败:Windows 用「进程 CWD 不可删/不可改名」,POSIX 用「父目录只读」)、投影缓存语义、审计流水、HTTP 状态码(200/400/401/404/503/500) | 88 passed / 0 failed |
| test/client.test.mjs | Client 半边:bundle 契约、槽位注册、确认对话框渲染与主题令牌、请求形状与重入保护、先藏行后删文件(含顺序证明)、拒绝时回滚、提示文案、绝不 reload | 51 passed / 0 failed |
| test/e2e-real.mjs | 真实存储上的可逆端到端:挑一个无归属会话,删前做全树 sha256 快照,走真实 POST /delete,验证归档发生在文件仍在时、trash 副本逐字节一致、投影缓存与审计流水,最后 finally 原样搬回并校验字节一致 | ALL PASSED |
⚠️ test/e2e-real.mjs 直接操作真实的 DSH_HOME,因此默认打印说明后退出,必须显式开启: bash
DSH_SESSION_DELETE_E2E=1 DSH_HOME=~/.dsh node test/e2e-real.mjs
它虽然始终还原(会话目录与投影缓存逐字节还原、归档集回滚),但仍会往真实的 deleted.jsonl 追加一条记录、并在回收站留下一个空条目 —— 这两处按设计不清理。它内部的 workspaceRegistry 是内存 stub(独立归档集,没有 sessionKnown/requireState/waterfall),所以它证明的是插件的顺序与字节级行为,不是真实注册表的契约。
为什么「删除」必须顺带归档,否则要重启才刷新
sessionQuery.listSessions()(@deepseek-ai/dsh-session-query 的 listSessions)先读持久层,再把常驻内存的会话叠加回去:
js
const records = await listPersisted(persistence, signal) // 磁盘上的会话
for (const session of this._ctx.sessions.list()) { // 已打开、常驻 host 内存的会话
records.set(session.id, { header: …, live: true, persisted: … })
}
而 ctx.sessions 只有 create/prepare/enter/announce/flush/get/list/fork,没有任何移除条目的方法,sessionController 也没有删除/驱逐接口。所以:一个已经打开过的对话,即使它的文件已经被删掉,在 DSH 重启(丢掉常驻内存)之前仍会被列出来。
侧边栏渲染时会按归档集过滤(dsh-client-ui-workspace/lib/client.js:357-367 的 sessionVisible,默认档 !archived.has(id)),所以归档是让这一行当场消失的唯一受支持手段。archiveSession 对「没有归属任何工作区」的会话同样有效(dsh-workspace/lib/index.js:507-540:"The session must exist (live or in session persistence); its workspace accounting — or lack of one — is irrelevant."),并且会顺手取消 pin。
发布 bash
npm version patch --no-git-tag-version # 或 minor / major
git commit -am "release: vX.Y.Z"
git tag -a vX.Y.Z -m "vX.Y.Z"
git push && git push origin vX.Y.Z
推 tag 会触发 release.yml:先跑 npm test,再把 lib/ + package.json + cordis.patch.yml + README.md + LICENSE 打成一个 zip 并附上 SHA256SUMS.txt,挂到对应 release 上(tag 还没有 release 就顺手建一个)。给已存在的 tag 补资产用 Actions 页面上的 Run workflow(workflow_dispatch 输入 tag 名),重复运行只覆盖资产。
卸载
删掉 cordis.patch.yml 里的 insert 段与 package.json 里的依赖,重启 DSH。回收站里的会话目录可以手动移回 sessions/<项目slug>/。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
nixuejun888/dsh-plugin-session-delete
在侧边栏菜单中彻底删除对话及其磁盘日志
相关插件 · 浏览器
智能体插件框架deepseek-ai/deepseek-harness
DeepSeek Harnessdsh是由 DeepSeek AI 开发的开源 agent harness智能体框架。
★ 270,422已汉化DSH
71.9
多设备 Agent 看板YaoApp/yao
✨ 所有 Agent 和工作区集中管理,覆盖你的每一台设备。任务看板追踪进度,桌面端、移动端、浏览器、API…
★ 8,147已汉化DSH
71.2
可扩展侧边栏工作台omdsh-dev/DSH-better-sidebar
DSH 的双工作台:原生右侧栏 + 自绘底部面板,并把 ctx.betterSidebar 服务开放给所有插件
★ 4,761已汉化DSH
71.1
浏览器自动化桥Tencent/BrowserSkill
你的 Agent。你的浏览器。连接,就能协作。
★ 9,967已汉化DSH
71
AI 小说创作工作台EthanYoQ/AI-Novel-Writer
AI 小说作家 / AI Novel Writer
★ 1,591已汉化DSH
70.7
智能体浏览器引擎platonai/Browser4
DeepSeek Harness 集成
★ 1,175已汉化DSH
70.4
这是你的仓库?认领条目