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

归档会话删除 Angela-lwh/dsh-archive-purge

DeepSeek Harness
Angela-lwh28 天前有提交已提供中文文档扫描:含敏感能力

为已归档会话补上删除按钮,安全清理日志与归档标记

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

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Angela-lwh/dsh-archive-purge
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · other
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
数据截至 2026/11/05·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/03·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 28 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 36 处,展示前 33 条)
  • 删除本地文件递归
    plugin/index.js:408
    await rm(plan.directory, { recursive: true, force: true, maxRetries: 3 })
  • 删除本地文件
    plugin/index.js:706
    await rm(path, { force: true })
  • 删除本地文件递归
    plugin/index.js:743
    await rm(spillSessionDirectory(root, sessionId), { recursive: true, force: true })
  • 读取本地文件
    plugin/index.js:767
    entries = await readdir(directory, { withFileTypes: true })
  • 读取本地文件
    plugin/index.js:783
    const stats = await stat(path)
  • 读取本地文件
    plugin/index.js:825
    if ((await stat(record.path).catch(() => undefined)) !== undefined) {
  • 删除本地文件
    plugin/index.js:826
    await rm(record.path, { force: true })
  • 删除本地文件递归
    plugin/index.js:1017
    await rm(directory, { recursive: true, force: true, maxRetries: 3 })
  • 删除本地文件递归
    plugin/index.js:1234
    await rm(directory, { recursive: true, force: true, maxRetries: 3 })
  • 写入本地文件
    plugin/index.js:1602
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    plugin/index.js:1604
    writeFileSync(temporary, `${JSON.stringify({ version: 1, sessionIds }, null, 2)}
  • 写入本地文件
    plugin/index.js:1606
    renameSync(temporary, path)
  • 读取本地文件
    plugin/index.js:1624
    const parsed = JSON.parse(readFileSync(path, 'utf8'))
  • 读取本地文件
    plugin/index.js:1657
    const parsed = JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    plugin/index.js:1667
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    plugin/index.js:1669
    writeFileSync(temporary, `${JSON.stringify({ version: PENDING_VERSION, sessionIds }, null, 2)}\n`, 'utf8')
  • 写入本地文件
    plugin/index.js:1670
    renameSync(temporary, path)
  • 读取本地文件
    plugin/index.js:1808
    const info = await stat(target).catch(() => undefined)
  • 发起网络请求
    plugin/lib/client.js:700
    const response = await fetch(new URL(ROUTE_PATH.slice(1), document.baseURI), {
  • 读取本地文件
    install.mjs:52
    if (!existsSync(join(source, 'index.js')) || !existsSync(join(source, 'lib', 'client.js'))) {
  • 读取本地文件
    install.mjs:55
    if (!existsSync(patchFile)) fail(`profile patch file not found: ${patchFile}`)
  • 删除本地文件递归
    install.mjs:63
    await rm(pluginDir, { recursive: true, force: true })
  • 写入本地文件
    install.mjs:64
    await mkdir(dirname(pluginDir), { recursive: true })
  • 读取本地文件
    install.mjs:74
    const revision = createHash('sha256').update(readFileSync(join(pluginDir, 'index.js'))).digest('hex').slice(0, 12)
  • 读取本地文件
    install.mjs:99
    const existing = existsSync(patchFile) ? await readFile(patchFile, 'utf8') : undefined
  • 读取本地文件
    install.mjs:99
    const existing = existsSync(patchFile) ? await readFile(patchFile, 'utf8') : undefined
  • 写入本地文件
    install.mjs:102
    await writeFile(patchFile, stripManagedBlock(existing), 'utf8')
  • 删除本地文件递归
    install.mjs:107
    await rm(pluginDir, { recursive: true, force: true })
  • 读取本地文件
    install.mjs:124
    const existing = await readFile(patchFile, 'utf8')
  • 写入本地文件
    install.mjs:127
    await writeFile(patchFile, `${existing.replace(/ *$/, '')}\n\n${block}\n`, 'utf8')
  • 写入本地文件
    install.mjs:130
    await writeFile(patchFile, stripManagedBlock(existing), 'utf8')
  • 写入本地文件
    install.mjs:133
    await writeFile(patchFile, `${settled.replace(/ *$/, '')}\n\n${block}\n`, 'utf8')
  • 写入本地文件
    install.mjs:159
    await copyFile(path, target)
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 归档会话加删除按钮
  • 清理会话日志与归档标记
  • 延迟模式处理占用会话

给已归档的 DeepSeek Harness 会话加删除按钮:清掉会话日志、工作区归属和归档标记,另带延迟模式,应对 Host 仍持有的会话。

README

由 DeepSeek 最新模型翻译生成
dsh-archive-purge —— 给已归档的 DSH 会话加一个删除按钮 DeepSeek Harness 能把会话归档,却从来不能删除会话。本工作区包含一个可用插件,为它补上这个动作,外加一套调研与测试工具——它们确定了怎么在一个正在运行的 Harness 上安全地做这件事。 状态:已在本机安装并验证(desktop profile,GUI 位于 http://127.0.0.1:19387)。 | 宿主行 | 活动 Loader 行 archive-purge-<revision> → fiberPhase: active | | 菜单项 | sidebar.workspaces.session.menu.item → archive-purge.delete,order 500(排在官方 Archive 400 之后)→ active | | 悬停按钮 | sidebar.workspaces.session.row.action → archive-purge.delete,order 300(排在官方 Archive 100、Pin 200 之后)→ active | | 路由 | POST /api/archive-purge/deleteSession → 走鉴权,全部探针通过 |
展开完整文档(README 剩余 1 节)
dsh-archive-purge —— 给已归档的 DSH 会话加一个删除按钮

DeepSeek Harness 能把会话归档,却从来不能删除会话。本工作区包含一个可用插件,为它补上这个动作,外加一套调研与测试工具——它们确定了怎么在一个正在运行的 Harness 上安全地做这件事。

状态:已在本机安装并验证(desktop profile,GUI 位于 http://127.0.0.1:19387)。

| 宿主行 | 活动 Loader 行 archive-purge-<revision> → fiberPhase: active |
| 菜单项 | sidebar.workspaces.session.menu.item → archive-purge.delete,order 500(排在官方 Archive 400 之后)→ active |
| 悬停按钮 | sidebar.workspaces.session.row.action → archive-purge.delete,order 300(排在官方 Archive 100、Pin 200 之后)→ active |
| 路由 | POST /api/archive-purge/deleteSession → 走鉴权,全部探针通过 |
| 补丁行 | $DSH_HOME/profiles/desktop/cordis.patch.yml,标记包裹的块,保留带时间戳的备份 |

延后删除在生产路径上跑通了:你确认删除的那个真实归档会话(标题不在此公开),在本次启动时由插件自动从磁盘移除 —— $DSH_HOME/archive-purge/pending.json 已清空,归档集合已清空,该会话目录已不存在。整个过程无需你再次点击。

首次真实使用,以及它暴露了什么

第一次点真实归档会话时被拒绝,错误码 archive-purge/session-live。诊断这个问题的绕路是值得的:

- 该会话仍被宿主加载着(空闲,没有在跑),因为你刚打开过它 —— 而只要被加载过,宿主就会持有它直到进程结束。
实测:tools/watch-liveness.mjs 连续轮询 6 分钟,已加载集合一次都没变;
- 插件无法卸载它:对已在存储里的 id,ctx.sessions.enter() 直接抛错;客户端的释放根本不会到达宿主; Agent 的注册被 agents 服务内部的 Cordis effect 持有;
- 那就硬删日志也不行:sessionQuery.listSessions() 是「内存存储 ∪ 已持久化 header」的并集,所以那一行会在下次刷新回来,而且因为没有日志而点不开。

于是插件长出了一个延后模式:被加载的会话,其删除会被登记(历史原样不动;归档标记保留,它既是授权也是撤销开关),并在下次宿主启动、无人持有时自动完成。弹窗用中英双语明确说明这一点。
本次启动正是这样把它删掉的。

同一趟绕路还挖出两个较小的事实,两者都已写进插件:桌面宿主不会把 DSH_HOME 导出给它自己的进程;而 !!js dshHomePath(...) 的 Loader 行在配置里保留着未求值的 {"__jsExpr": …} 标记 ——因此 Harness home 改为从持久化层自己 locate() 出来的真实路径反推。

怎么用

要在某个已归档会话上看到按钮:

1. 把侧栏视图筛选切到 仅显示已归档(或 全部对话);
2. 悬停该行点垃圾桶图标,或打开它的 ... 菜单选 删除会话;
3. 在弹窗里确认。宿主已释放的会话会立即消失(宿主推送 api-session/removed),日志目录一并删除;宿主仍持有的会话则被登记,下次启动自动移除 —— 弹窗会明确这样写。

如果按钮没出现,刷新一次页面(Ctrl+R)。宿主侧不需要重启:该 profile 是热补丁 profile,行已经是 active。

已登记的删除记在 $DSH_HOME/archive-purge/pending.json,下次启动的延后执行会把它清空。
在此之前取消归档即可撤销一次已登记的删除。想从命令行登记一个会话,用 node tools/purge.mjs <sessionId>;想看当前登记了什么,读那个 JSON 文件。

目录结构

| 路径 | 说明 |
| plugin/ | 插件本体:index.js(宿主半)、lib/client.js(手写的浏览器 bundle)、package.json、cordis.patch.yml、README.md |
| plugin/README.md | 插件自己的文档 —— 设计、扩展点、删什么不删什么、限制 |
| install.mjs | 单 profile 的安装 / 卸载 / dry-run,Loader 行带 revision |
| tools/test-host.mjs | 宿主半对临时会话根:所有拒绝分支 + 一次真实的递归删除 |
| tools/test-client.mjs | 浏览器 bundle 对假的 __ModuleLoader__、模块表、React 与 fetch |
| tools/test-route.mjs | 正在运行的宿主端到端:信任栅栏、cookie、路由、JSON 契约 |
| tools/e2e-delete.mjs | 用产品自己的 API 建一个一次性会话并归档,然后探测各条保护 |
| tools/e2e-forged-delete.mjs | 真实破坏性测试(--staged 走延后路径):制造一个真实存储的会话,交给插件删除,核验磁盘与全部持久状态并清理 |
| tools/purge.mjs | 从命令行调用删除路由 |
| tools/remote.mjs | 从命令行调用任意 Remote namespace/method(用文件传参,避免 PowerShell 引号把 JSON 弄坏) |
| tools/watch-liveness.mjs | 观察宿主是否会自行释放已加载的会话 |
| tools/verify-paths.mjs | 会话目录推导,对照每一个真实会话 header |
| tools/cycle.mjs | 在热 profile 上强制一次全新的宿主侧导入 |
| tools/asar.mjs | 列出 / 读取 / 提取发行版 app.asar 内部文件 |
| vendor/ | 本次工作依赖的 DSH 包的只读提取 |
| REPORT-host-rpc.md、REPORT-delete-archived-session.md | 两份调研报告:客户端→宿主扩展点,以及会话/Workspace 的持久状态 |

调研确认了什么

以下几条都不在 DSH 的公开文档里,而每一条都改变了设计:

1. DSH 文档自己指向的扩展点 ctx.connection.rpc.handle(),在这个版本里是坏的。 它通过 Connection 服务自身的 Context 去读 webServer,而那个 Context 的 inject 里没有声明该服务,于是被 Cordis 的严格断言在 register() 内部直接拒绝: cannot get property "webServer" without inject(dsh-client-connection/lib/index.js:656)。
表现是整条插件行变成 FAILED fiber,且任何地方都看不到原因。可用的是
ctx.connection.fetch.register():挂在共享 /api Fetch handler 上的精确路由 ——
在 Gateway 的 Remote 拦截器之前匹配,并且免费继承鉴权,因为 /api 前缀路由在分发前会先跑
connection.admit()(Host/Origin 栅栏 → 签名 cookie)。

2. 删除会话=删掉那个目录,而且只能是那个目录。 全库没有任何删除 API: sessionPersistence 的接口只有 create/open/stat/list/flush;workspaceRegistry.delete() 删的是 Workspace 注册。一个会话恰好拥有
<sessionsRoot>/<projectKey(cwd)>/<encodeSegment(id)>/ —— 里面只有日志世代,Windows 上连锁文件都没有。
真正需要一并修改的持久存储只有 $DSH_HOME/storages/workspace.json(workspace 域:成员关系、归档集合、置顶集合),通过 Workspace.detachSession() 与 workspaceRegistry.unarchiveSession() 触达。
SQLite 搜索索引在本机是 :memory: / openAt: never,启用时也会自愈;投影缓存行是惰性的;附件是共享的内容寻址对象,绝不能碰。

3. 被加载的会话无法移除,而它正是第一次真实点击失败的原因。 宿主会把它曾经加载过的每个会话持有到进程结束—— 实测:用 tools/watch-liveness.mjs 空闲轮询 6 分钟,已加载集合从未变化。没有任何插件可及的 API 能卸载它(对已在存储里的 id,ctx.sessions.enter() 抛错;客户端的释放不会到达宿主),而硬删其日志会产生一行「下次刷新就回来、且点不开」的坏行,因为 sessionQuery.listSessions() 是内存存储与已持久化 header 的并集。
这就是延后模式存在的原因。

4. 宿主自己的进程里没有 DSH_HOME,Loader 行里也没有求值后的 config root。 process.env.DSH_HOME 在宿主内部是 undefined(它只注入工具子进程);!!js dshHomePath('sessions') 行在 options.config 里保留着未求值的 {"__jsExpr":"dshHomePath('sessions')"} 标记;运行时也不存在 dshHomePath 全局。因此路径改为从持久化层自己的 locate() 结果推导,并且每一层 dirname 都写在具名辅助函数里。

另外一条同样实测得到、并写进安装脚本的结论:DSH 的 Loader 不会为一个它已认识的 entry id 重新求值模块,而 Node 的 ESM 缓存以 URL 为键 —— 所以要让改动在运行中的进程里生效,行的 id 与模块 URL 都必须带新 revision。
调试时值得知道的一个推论:把同一行重写一遍不会重新执行 apply,tools/cycle.mjs 就是为此存在的。

复现验证

node tools/test-host.mjs # 宿主半:保护分支、登记、启动清账、真实递归删除
node tools/test-client.mjs # 浏览器半:插槽、inject 面、组件输出、路由调用、文案
node tools/verify-paths.mjs # 每个已存储会话都能解析到它自己的目录
node tools/test-route.mjs # 运行中的宿主:401 / 403 / 403 / 200 + 各拒绝码
node tools/e2e-forged-delete.mjs --template <id> # 真实破坏性测试
node tools/e2e-forged-delete.mjs --template <id> --staged # 延后路径

破坏性测试,以及它的两个陷阱

通过 session/create 创建的会话必然是活的(agents.ensureSession 只返回活的普通 Agent),而插件会(正确地)拒绝活会话 —— 所以第一次尝试返回 archive-purge/session-live,且什么都没删。
若为了「校验日志」去调 session/rename,同样会把它变成活的,因为「以写入方式打开一个已存储会话」就是加载它。
因此 tools/e2e-forged-delete.mjs 用一份合法日志制造出一个已存储、非活的会话(只改 header 里的 id),用 session/list 校验它 —— 这与删除路径所需的 stat() 是同一次 header 读取 —— 然后交给插件真删:

ok a stored log now exists under the new identity
ok DSH lists the manufactured Session (cwd=<project-dir>)
ok it is reported as stored, not live
ok workspace/archiveSession succeeds
ok the plugin deleted it ({deleted:true, directory:"…\--<project-slug>--\session-…"}) ok the log directory is gone ok the archive mark was cleared ok the Workspace membership was pruned ok DSH no longer lists it ok the archive set holds exactly what it did before

如果 DSH 没有列出被制造的会话,脚本会在任何删除动作之前中止;无论发生什么,它的 finally 都会清掉目录并撤销归档标记。事后核验所有既有会话完好(15/15 目录自洽,5/5 Workspace 注册未变)。

另外两条保护性拒绝也在真实数据上跑过:活会话,以及一个已归档的 subagent 会话(archive-purge/subagent,通过先归档再撤销归档验证 —— 完全可逆)。

回滚

node install.mjs --uninstall # 移除 Loader 行与 $DSH_HOME/plugins/dsh-archive-purge

每次安装前都会在 cordis.patch.yml 旁留一份带时间戳的副本(cordis.patch.yml.bak-<timestamp>),仅凭那份文件就能回滚任何一次安装。
预览分享卡片长什么样 ↓Angela-lwh/dsh-archive-purge
分享 · DeepSeek Harness Hub
Angela-lwh/dsh-archive-purge

为已归档会话补上删除按钮,安全清理日志与归档标记

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部