在侧栏会话菜单加删除项,级联清理子代理并保护分叉会话
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Nay-1/dsh-session-menu-delete该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · other
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 35 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(unknown · 2026/9/30) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装仓库 package.json 标记 private,未发布到 npm,需从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-session-menu-delete(未发布到 npm,仅可源码安装)
✓Node 引擎要求 ^22.15.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/8 09:40:58
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 82 处,展示前 29 条)
- 读取本地文件lib/index.js:169
stream = createReadStream(file).pipe(api.create()); - 读取本地文件lib/index.js:199
const stream = createReadStream(file, { encoding: "utf8", highWaterMark: 64 * 1024 }); - 读取本地文件lib/index.js:276
buffer = await readFile(file); - 读取本地文件lib/index.js:377
slugs = await readdir(sessionsRoot, { withFileTypes: true }); - 读取本地文件lib/index.js:385
const details = await stat(candidate); - 读取本地文件lib/index.js:397
slugs = await readdir(sessionsRoot, { withFileTypes: true }); - 删除本地文件lib/index.js:719
await rmdir(shard); - 删除本地文件lib/index.js:743
await rm(object.file, { force: true }); - 删除本地文件lib/index.js:1083
await rm(file, { force: true }); - 删除本地文件递归lib/index.js:1116
await rm(dir, { recursive: true, force: true }); - 发起网络请求lib/client.js:125
fetch(`${API}/delete`, { - 写入本地文件test-host.mjs:28
await mkdir(cacheDir, { recursive: true }); - 写入本地文件test-host.mjs:33
await mkdir(dir, { recursive: true }); - 写入本地文件test-host.mjs:46
await writeFile(join(dir, "session.v4.jsonl.zstd"), Buffer.concat(frames)); - 写入本地文件test-host.mjs:47
await writeFile(join(cacheDir, `${id}.json`), JSON.stringify({ version: 7, record: {} })); - 写入本地文件test-host.mjs:83
await mkdir(dir, { recursive: true }); - 写入本地文件test-host.mjs:85
await writeFile(file, Buffer.from(`payload-${hash.slice(0, 8)}`, "utf8")); - 读取本地文件test-host.mjs:264
check("工件目录已删除", !existsSync(join(sessionsDir, coldId))); - 读取本地文件test-host.mjs:265
check("投影缓存记录已删除", !existsSync(join(cacheDir, `${coldId}.json`))); - 读取本地文件test-host.mjs:295
check("活会话工件也已删除", !existsSync(join(sessionsDir, liveId))); - 读取本地文件test-host.mjs:328
check("父会话工件已删除", !existsSync(join(sessionsDir, parentId))); - 读取本地文件test-host.mjs:329
check("子代理 childA 工件已删除", !existsSync(join(sessionsDir, childA))); - 读取本地文件test-host.mjs:330
check("子代理 childB 工件已删除", !existsSync(join(sessionsDir, childB))); - 删除本地文件test-host.mjs:721
await rm(goneFile, { force: true }); - 发起网络请求test-host.mjs:838
const noHeader = await fetch(`${base}/health`); - 发起网络请求test-host.mjs:841
const withHeader = await fetch(`${base}/health`, { headers: { "x-dsh-plugin-call": "1" } }); - 发起网络请求test-host.mjs:844
const localPost = await fetch(`${base}/delete`, { - 发起网络请求test-host.mjs:851
const evil = await fetch(`${base}/health`, { - 发起网络请求test-host.mjs:856
const sameOrigin = await fetch(`${base}/health`, {
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 侧栏会话菜单加删除项
- 级联清理子代理
- 保护分叉会话
DSH 侧栏会话行菜单里的「删除会话」:级联清理子代理、保护 fork 会话
README
由 DeepSeek 最新模型翻译生成dsh-session-menu-delete
给 DeepSeek Harness 侧栏「会话行 ⋯ 菜单」加一项 删除会话 —— 位置紧跟在「归档会话」下方,带垃圾桶图标,红色破坏性样式。
DSH 原生只有「归档」,没有删除;这个插件把它补上,并且连带清理子代理会话(不留孤儿),同时保护 fork 出来的会话。
它加在哪
那个菜单就是插槽 sidebar.workspaces.session.menu.item,内置四项的 order 是:
| 条目 | 来源 | order |
| 置顶会话 | 内置 | 100 |
| 重命名 | 内置 | 200 |
| 分叉会话 | 内置 | 300 |
| 归档会话 | 内置 | 400 |
| 删除会话 | 本插件 | 500 |
图标用 primitives 的 IconTrashOutlineRegular(与内置菜单项同一套),并以 danger: true 渲染成破坏性操作的红字。
点了会删什么
点击即删,没有二次确认。
展开完整文档(README 剩余 1 节)
dsh-session-menu-delete
给 DeepSeek Harness 侧栏「会话行 ⋯ 菜单」加一项 删除会话 —— 位置紧跟在「归档会话」下方,带垃圾桶图标,红色破坏性样式。
DSH 原生只有「归档」,没有删除;这个插件把它补上,并且连带清理子代理会话(不留孤儿),同时保护 fork 出来的会话。
它加在哪
那个菜单就是插槽 sidebar.workspaces.session.menu.item,内置四项的 order 是:
| 条目 | 来源 | order |
| 置顶会话 | 内置 | 100 |
| 重命名 | 内置 | 200 |
| 分叉会话 | 内置 | 300 |
| 归档会话 | 内置 | 400 |
| 删除会话 | 本插件 | 500 |
图标用 primitives 的 IconTrashOutlineRegular(与内置菜单项同一套),并以 danger: true 渲染成破坏性操作的红字。
点了会删什么
点击即删,没有二次确认。 client 半发 POST /session-menu-delete/api/delete,host 半依次处理:
1. 会话工件目录 /sessions///
2. 投影缓存记录 /storages/session_projcache/sessions/.json
3. 工作区账本归属 —— 走 workspaceRegistry 服务的 entity.detachSession(),而不是直接改 workspace.json(否则 DSH 的内存态会在下次写盘时把手改的内容覆盖回去)
4. 归档 / 置顶集合里的该 id
5. 它派生出的子代理会话(级联,见下)
6. 它独享的附件对象(顺手清理,见下 —— 被别的会话共享的一律不动)
会话仍在运行时,先按 DSH 自己的 retire 顺序摘除(agent.cancel({kind:'disposed'}) → scope.dispose() → agents.store.delete() → sessions 条目 detach()),再删文件,避免留下“文件没了但注册表还在”的僵尸。
⚠️ 不可恢复:会话工件没有回收站,删掉就是删掉。唯一有后悔药的是第 6 步的附件 —— 它们进 attachments/trash/,可按哈希还原。
删掉「正在看的那个会话」不会把界面留在死状态
主视图(右侧对话面板)由 ui-workspace 的 uiWorkspace 服务持有,钉在一个 mainReference 上。DSH 只在会话被归档时让它自动让位(clearArchivedCurrent)—— 会话被彻底删除时不会,引用还指着已经不存在的 id,输入框就退化成「会话不可用」。
所以 client 半在删除前先取两个快照,删完按下述规则处理:
| 删的是 | 行为 |
| 当前正在看的会话 | 立刻开一个新会话(与侧栏「新会话」按钮同一条路:uiWorkspace.startSession(workspaceId),复用该工作区的空白会话,没有就新建) |
| 其它会话 | 什么都不做,你正在看的不受影响 |
三个细节:
- 工作区跟着走:新会话开在被删会话所属的那个工作区里。
- 工作区没了就不硬塞:若该工作区已随会话一起消失,改传 undefined,交给内置的“就近工作区”兜底,而不是让内置流程弹一个错误提示。
- 兜底不留死界面:若 4 秒后主视图仍钉在已删 id 上(新会话没开起来),直接 clearMain() 退到空态 —— 宁可空白,也不给你一个「会话不可用」的输入框。
uiWorkspace 是软依赖:走 ctx.get("uiWorkspace")(cordis 语义下不受 inject 约束、缺失返回 undefined),所以拿不到它只会让跳转静默失效,不会拖垮删除本身。
级联删除:只链子代理,绝不碰分叉
子代理会话是独立落盘的会话工件(与父会话平级放在同一个 slug 目录里),header 长这样:
{ "id": "", "parentSession": "", "origin": "subagent", "delegationDepth": 1 }
不做级联就会留下孤儿:父会话没了、子代理却还在磁盘上,甚至继续作为独立会话行出现在侧栏。所以删除时会:
1. 扫全部会话工件的首行 header —— zstd 只解压到第一个换行就停,不展开整份日志;
2. 只把 origin === 'subagent' 的会话放进血缘图,顺着 parentSession 递归收集后代(子代理还能再起子代理,深度 2、3…);
3. 先删后代、再删自己。
主会话 ─┬─ 子代理 → 一起删 ✓ └─ fork 出来的副本 → 保留 🔒 └─ 它的子代理 → 也保留 🔒(级联不穿过 fork)
🔒 分叉会话受保护:用户主动 fork 出来的会话同样带 parentSession,但没有 origin 标记 ——它们永远不进血缘图,删父会话时一律保留。这条边界有专门的测试用例守着。
fork 出来的会话是自包含的:分叉那一刻,历史以数据形式(seed)写进新工件,seedLength 只是水位计数、不是指向父文件的引用。所以删掉父会话,副本的内容完整、可以继续对话;唯一的残留是它 header 里的 parentSession 变成悬空血缘(仅元数据,不影响使用)。
想只删目标会话、不动它的子代理,请求体加 {"cascade": false}。
想先看看会连带删掉谁,GET /session-menu-delete/api/lineage?sessionId= 会列出全部后代。
附件孤儿清理:看一眼就等于引用一次
附件不塞在会话日志里,而是内容寻址的独立对象:
/attachments/v1/objects//
日志里只留一句引用。好处是同一张图只存一份(去重);代价是删会话只摘引用、不动物件,于是攒下没人引用的孤儿。
清理有两个入口,判断标准完全一致:
| 入口 | 触发方式 | 清理范围 |
| 随会话删除顺手清(默认开启) | 点菜单里的「删除会话」 | 只清引用者全部落在本次被删集合内的附件(含级联的子代理);被 fork 副本或其它会话共享的一律不动 |
| 独立清理 | POST /attachments/clean | 全库范围内所有孤儿 |
两者都遵守下面的安全规则,也都移入回收站而不是直删。顺手清理可用请求体 {"attachments": false} 关掉。
为什么删会话不干脆"把它引用过的图全清掉"?因为附件是共享的:fork 会复制历史,历史里的图片引用也跟着复制。若照着"这个会话引用过哪些图"去删,副本里的图会集体变裂图。判据必须是引用者是否全部落在此次被删的集合内,而不是"这个会话引用过它"。
清理这件事有个反直觉的坑:"看一眼"就等于"引用一次"。用 read_image 读一张图,DSH 会把它登记成该会话的图片附件并写进事件流 —— 若删除依据是一次陈旧的扫描,就会删掉一个其实已被引用的对象,之后组装模型请求时报 Attachment object is missing.(真踩过)。所以清理走四条规则:
1. 删除瞬间重算引用,绝不复用上一次的扫描结果;
2. 结构化解析 attachmentId(递归遍历事件 JSON,含 tool/result 里的图片),不做文本匹配 —— 文本搜 64 位 hex 会把日志里任何偶然的长十六进制串都当成引用;
3. 新鲜度保护:mtime 在窗口内(默认 15 分钟)的对象一律跳过 —— 刚上传、刚被看一眼的图,其引用可能还没落盘;
4. 移入回收站而不是直删:/attachments/trash//,可按哈希精确还原(对象名就是内容哈希)。
1. 先看会清理谁(只读,不动文件) Invoke-RestMethod "http://127.0.0.1:19387/session-menu-delete/api/attachments/orphans" | ConvertTo-Json -Depth 5
2. 确认后移入回收站(不带 confirm 同样只预览) Invoke-RestMethod -Method Post "http://127.0.0.1:19387/session-menu-delete/api/attachments/clean" -ContentType "application/json" -Body '{"confirm":true}' | ConvertTo-Json -Depth 5
3. 后悔了?按哈希还原回内容寻址目录 Invoke-RestMethod -Method Post "http://127.0.0.1:19387/session-menu-delete/api/attachments/restore" -ContentType "application/json" -Body '{"hash":""}'
4. 确认不再需要了?彻底清空回收站(不带 confirm 同样只预览) Invoke-RestMethod -Method Post "http://127.0.0.1:19387/session-menu-delete/api/attachments/trash/purge" -ContentType "application/json" -Body '{"confirm":true}' 也可只清一个批次:-Body '{"batch":"2026-09-29T20-04-27-979Z","confirm":true}'
清空回收站是安全操作:能进回收站的附件都已经被移出内容寻址目录、没有任何会话在引用,所以这一步不需要引用判定,confirm 防的是手滑而不是误判。batch 只与已列出的目录名精确匹配,拼不出越界路径。
安装
把本仓库放到本地任意目录,然后用它的绝对路径安装:
dsh plugin --profile desktop add "file:"
dsh plugin --profile 会把参数转发给 pnpm,所以包由 pnpm 管理(nodeLinker: hoisted)。
装完还必须让 DSH 知道要加载它,二选一:
- 在 DSH 的 「插件」页面里启用(会写 dsh.profile.bundles);
- 或手工把 "dsh-session-menu-delete" 加进 ~/.dsh/profiles/desktop/package.json 的 dsh.profile.bundles 数组。
改动需要重启 DSH 生效(bundle 与 client 半都在启动时加载)。
file: 依赖是「拷贝」安装,而且 pnpm 不会替你刷新这份拷贝。(nodeLinker: hoisted,实测 pnpm 11.7.0)改完源码后 add / install / install --force 都会回一句 Already up to date 并留下旧副本;把 node_modules/dsh-session-menu-delete 删掉再 install 也不会重建(随后 DSH 启动会报 cannot resolve profile bundle)。可靠的做法是删掉旧目录后手动同步一次:
$src = ""; $dst = "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-session-menu-delete"
Remove-Item -Recurse -Force $dst
Copy-Item -Recurse -Force $src $dst
Remove-Item -Recurse -Force "$dst\.git"
校验是否同步到位(两边哈希应一致):
(Get-FileHash "$src\lib\client.js").Hash; (Get-FileHash "$dst\lib\client.js").Hash
卸载
dsh plugin --profile desktop remove dsh-session-menu-delete
再从 dsh.profile.bundles 里删掉 "dsh-session-menu-delete" 这一行,重启 DSH 即完全还原。
HTTP 端点
| 方法与路径 | 作用 |
| POST /session-menu-delete/api/delete | 删除会话(默认级联子代理);body { sessionId, cascade? } |
| GET /session-menu-delete/api/lineage?sessionId= | 预演级联:列出该会话全部子代理后代 |
| GET /session-menu-delete/api/attachments/orphans | 预览附件孤儿(只读,不动文件) |
| POST /session-menu-delete/api/attachments/clean | 把孤儿移入回收站;body { confirm: true, freshMinutes? } |
| GET /session-menu-delete/api/attachments/trash | 回收站清单(按批次分组) |
| POST /session-menu-delete/api/attachments/trash/purge | 彻底清空回收站;body { confirm: true, batch? } |
| POST /session-menu-delete/api/attachments/restore | 按哈希还原;body { hash } |
| GET /session-menu-delete/api/health | 存活检查,回显各根目录与能力标记 |
结构
| 文件 | 作用 |
| lib/index.js | host 半:cordis 插件,会话删除 + 血缘扫描 + 附件孤儿清理 + HTTP 端点 |
| lib/client.js | client 半:Module Loader 包(factory(require),无构建步骤),注册菜单项 |
| cordis.patch.yml | bundle 层 patch:往插件树里 insert 一行 |
| test-host.mjs | host 半离线冒烟测试(76 项:级联 / fork 保护 / 附件清理 / 顺手清理 / 回收站清空) |
| test-client.mjs | client 半离线冒烟测试(34 项:菜单注册 / 点击链路 / 删当前会话后开新会话 / 各种兜底) |
测试用临时 DSH_HOME + mock 服务跑,工件用真实的多帧 zstd 写(header 帧 + 正文帧),级联血缘走的是与线上完全相同的解压路径;全程不碰真实会话数据: powershell
node test-host.mjs
node test-client.mjs
已知边界
- 点击即删:没有二次确认,误点无法撤回(仅在删除失败时 alert 报错)。
- 删掉当前会话时会自动开一个新会话;uiWorkspace 服务缺失或新会话开不起来时退到空态,不会出现「会话不可用」的死界面(详见上文)。
- 级联只认 origin === 'subagent';fork 出来的会话(有 parentSession、无 origin)一律不删。
- 附件清理只动 attachments/v1/objects 下的对象,判据是会话日志里的结构化引用;"界面上正在显示、但引用尚未落盘"的图由新鲜度窗口兜底(窗口内一律不碰)。回收站不会自动过期清理(后悔药不该自己过期),确认无误后用 POST /attachments/trash/purge 手动清空。
- 运行时若 node:zlib 没有 zstd 支持,血缘扫描读不到 header,级联会退化为只删目标会话;附件清理同样读不到引用,此时会一个都不删(保守方向,不会误删)。
- client 半优先用 @deepseek-ai/dsh-client-ui-primitives 的 MenuItemButton;该组件缺失时退回自绘 role="menuitem" 按钮,不会让侧栏渲染崩掉。
- file: 依赖指向本目录。移动或删除这个源目录后,需要重新 dsh plugin add 才能再安装/更新;平时改源码则按上文「安装」一节的同步步骤手动刷新副本。
License
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Nay-1/dsh-session-menu-delete
在侧栏会话菜单加删除项,级联清理子代理并保护分叉会话
相关插件 · 其他
模型破甲清洗HunYiSage/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 16,993已汉化DeepSeek 客户端
73
女娲蒸馏alchaincyf/nuwa-skill
输入名人名字,提炼其思维模型与决策方式供你调用
★ 37,308已汉化DSH
72
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 888已汉化DSH
71.6
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,759已汉化DSH
71.4
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,269已汉化DSH
70.8
这是你的仓库?认领条目