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

历史归档管理 LianbinZhou/dsh-archive-manager

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

列出已归档会话,一键恢复或彻底删除

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · market
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
L4 · 真实安装等级说明 →
数据截至 2026/10/15·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/05·判据 →
还在维护吗[仓库]—
更新放缓:最近一次提交在 31 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

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

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

读写/删除本地文件发起外部网络请求
查看扫描证据(共 58 处,展示前 50 条)
  • 发起网络请求
    lib/client.js:254
    fetch("/api/archive-manager/list")
  • 发起网络请求
    lib/client.js:290
    const resp = await fetch("/api/archive-manager/unarchive", {
  • 发起网络请求
    lib/client.js:326
    const resp = await fetch("/api/archive-manager/delete", {
  • 读取本地文件
    lib/paths.js:96
    projects = readdirSync(sessionsRoot);
  • 读取本地文件
    lib/paths.js:104
    segments = readdirSync(projectPath);
  • 读取本地文件
    lib/paths.js:112
    if (!statSync(dirPath).isDirectory()) continue;
  • 删除本地文件递归
    lib/paths.js:137
    rmSync(real, { recursive: true, force: false });
  • 读取本地文件
    lib/paths.js:153
    if (existsSync(indexPath)) {
  • 读取本地文件
    lib/paths.js:154
    const parsed = JSON.parse(readFileSync(indexPath, "utf8"));
  • 写入本地文件
    lib/paths.js:166
    if (touched) writeFileSync(indexPath, JSON.stringify(parsed, null, 2), "utf8");
  • 读取本地文件
    lib/paths.js:177
    if (existsSync(file)) unlinkSync(file);
  • 删除本地文件
    lib/paths.js:177
    if (existsSync(file)) unlinkSync(file);
  • 读取本地文件
    activate.mjs:7
    let raw = readFileSync(p, "utf8");
  • 写入本地文件
    activate.mjs:13
    writeFileSync(p, JSON.stringify(j, null, 2) + "\n", "utf8");
  • 读取本地文件
    build-client.mjs:8
    const src = readFileSync(join(root, "src", "client", "client.js"), "utf8");
  • 写入本地文件
    build-client.mjs:29
    writeFileSync(join(root, "lib", "client.js"), wrapper, "utf8");
  • 读取本地文件
    build.mjs:21
    for (const entry of readdirSync(hostSrc)) {
  • 写入本地文件
    build.mjs:23
    copyFileSync(join(hostSrc, entry), join(lib, entry));
  • 读取本地文件
    build.mjs:27
    const src = readFileSync(join(root, "src", "client", "client.js"), "utf8");
  • 写入本地文件
    build.mjs:48
    writeFileSync(join(lib, "client.js"), wrapper, "utf8");
  • 写入本地文件
    delete-service-test.mjs:40
    mkdirSync(sessionDir, { recursive: true });
  • 写入本地文件
    delete-service-test.mjs:41
    mkdirSync(otherDir, { recursive: true });
  • 写入本地文件
    delete-service-test.mjs:42
    writeFileSync(join(sessionDir, "session.v3.jsonl.zstd"), "bytes");
  • 写入本地文件
    delete-service-test.mjs:43
    writeFileSync(join(otherDir, "session.v3.jsonl.zstd"), "bytes");
  • 写入本地文件
    delete-service-test.mjs:44
    mkdirSync(join(home, "storages", "session_projcache", "sessions"), { recursive: true });
  • 写入本地文件
    delete-service-test.mjs:45
    writeFileSync(join(home, "storages", "session_projcache.json"), JSON.stringify({ tables: { sessions: { [sessionId]: {}, [otherId]: {} } } }), "utf8");
  • 读取本地文件
    delete-service-test.mjs:76
    check("被拒绝时目录未被删", existsSync(sessionDir), true);
  • 读取本地文件
    delete-service-test.mjs:86
    check("会话目录被物理删除", existsSync(sessionDir), false);
  • 读取本地文件
    delete-service-test.mjs:87
    check("逐会话缓存文件被删除", existsSync(join(home, "storages", "session_projcache", "sessions", `${sessionId}.json`)), false);
  • 读取本地文件
    delete-service-test.mjs:89
    check("无关会话目录保留", existsSync(otherDir), true);
  • 读取本地文件
    delete-service-test.mjs:101
    check("裸 uuid 目录被删除", existsSync(bareDir), false);
  • 删除本地文件递归
    delete-service-test.mjs:112
    rmSync(home, { recursive: true, force: true });
  • 写入本地文件
    delete-test.mjs:67
    mkdirSync(sessionDir, { recursive: true });
  • 写入本地文件
    delete-test.mjs:68
    writeFileSync(join(sessionDir, "session.v3.jsonl.zstd"), "session-bytes");
  • 写入本地文件
    delete-test.mjs:72
    mkdirSync(bareDir, { recursive: true });
  • 写入本地文件
    delete-test.mjs:73
    writeFileSync(join(bareDir, "session.v3.jsonl.zstd"), "bare-bytes");
  • 写入本地文件
    delete-test.mjs:75
    mkdirSync(join(home, "storages", "session_projcache", "sessions"), { recursive: true });
  • 写入本地文件
    delete-test.mjs:76
    writeFileSync(
  • 读取本地文件
    delete-test.mjs:127
    check("逃逸目标文件仍在", existsSync(join(escapeTarget, "secret.txt")), true);
  • 读取本地文件
    delete-test.mjs:135
    const indexAfter = JSON.parse(readFileSync(join(home, "storages", "session_projcache.json"), "utf8"));
  • 读取本地文件
    delete-test.mjs:138
    check("投影缓存单文件已删除", existsSync(join(home, "storages", "session_projcache", "sessions", `${sessionId}.json`)), false);
  • 读取本地文件
    delete-test.mjs:142
    check("会话目录已物理删除", existsSync(sessionDir), false);
  • 读取本地文件
    delete-test.mjs:143
    check("sessions 根仍然存在", existsSync(sessionsRoot), true);
  • 读取本地文件
    delete-test.mjs:159
    const indexSource = readFileSync(new URL("./src/host/index.js", import.meta.url), "utf8");
  • 删除本地文件递归
    delete-test.mjs:186
    rmSync(home, { recursive: true, force: true });
  • 写入本地文件
    host-test.mjs:12
    writeFileSync(new URL("./_host_test.txt", import.meta.url), out.join("\n"));
  • 读取本地文件
    loader-test.mjs:5
    const code = readFileSync(new URL("./lib/client.js", import.meta.url), "utf8");
  • 写入本地文件
    loader-test.mjs:53
    writeFileSync(new URL("./_loader_test.txt", import.meta.url), out.join("\n"));
  • 读取本地文件
    title-test.mjs:19
    let code = readFileSync(LIB, "utf8");
  • 写入本地文件
    title-test.mjs:217
    writeFileSync(new URL("./_title_test.txt", import.meta.url), report, "utf8");
扫描于 2026/11/5(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

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

README

由 DeepSeek 最新模型翻译生成
dsh-archive-manager DSH Web GUI 的「历史归档」管理插件:侧边栏新增「历史归档」入口,列出已归档的会话,支持一键恢复回工作区,以及带二次确认的物理删除。 - 侧边栏「历史归档」入口(跟随 DSH 侧边栏样式) - 面板列出所有归档会话(显示真实会话标题 + 归档时间) - 每条记录一个「恢复」按钮,一键取消归档、回到工作区列表 - 每条记录一个「删除」按钮,二次确认后彻底删除会话(不可恢复) - 面板打开时自动刷新 + 打开状态下每 3 秒轮询,归档/恢复/删除无需刷新页面 背景 DSH 官方提供「归档会话」(workspace.archiveSession),但既没有恢复(unarchive)入口,也没有物理删除入口——归档后的会话会从工作区列表隐藏,官方没有任何界面能把它放回来,更没有办法真正删掉它。本插件补上这两个缺口: - 宿主端注册 /api/archive-manager/list、/api/archive-manager/unarchive、/api/archive-manager/delete - 客户端注入侧边栏入口 + 面板 删除的语义与安全边界 物理删除不可恢复。
展开完整文档(README 剩余 1 节)
dsh-archive-manager

DSH Web GUI 的「历史归档」管理插件:侧边栏新增「历史归档」入口,列出已归档的会话,支持一键恢复回工作区,以及带二次确认的物理删除。

- 侧边栏「历史归档」入口(跟随 DSH 侧边栏样式)
- 面板列出所有归档会话(显示真实会话标题 + 归档时间)
- 每条记录一个「恢复」按钮,一键取消归档、回到工作区列表
- 每条记录一个「删除」按钮,二次确认后彻底删除会话(不可恢复)
- 面板打开时自动刷新 + 打开状态下每 3 秒轮询,归档/恢复/删除无需刷新页面

背景

DSH 官方提供「归档会话」(workspace.archiveSession),但既没有恢复(unarchive)入口,也没有物理删除入口——归档后的会话会从工作区列表隐藏,官方没有任何界面能把它放回来,更没有办法真正删掉它。本插件补上这两个缺口:

- 宿主端注册 /api/archive-manager/list、/api/archive-manager/unarchive、/api/archive-manager/delete
- 客户端注入侧边栏入口 + 面板

删除的语义与安全边界

物理删除不可恢复。 插件按下面的顺序执行,任一步失败都不会留下"半删除"的会话:

1. 归档标记:从 workspace 存储域的 archivedSessionIds 中移除(复用恢复的写入路径,侧边栏与已连接的浏览器立即同步)
2. 工作区记录:从每个工作区实体的 sessionIds 名册中移除(尽力而为,失败不阻塞后续步骤)
3. 存储目录:删除 $DSH_HOME/sessions/<project>/<session-id>/(含 session.v3.jsonl.zstd)
4. 投影缓存:清理 storages/session_projcache.json 的索引行,以及该会话的逐会话缓存文件

安全约束:

- 只有已归档的会话能被删除。 正在使用的会话在归档之前不可达,误发或重放的请求也删不掉它。
- 删除前对路径做 realpath 二次校验,拒绝任何位于 $DSH_HOME/sessions 之外的路径。
- 不跟随逃逸符号链接:指向 sessions 根之外的链接既不会被索引,也不会被删除。
- 会话 id 先做格式校验(拒绝 ..、路径分隔符、超长输入)才触碰文件系统。
- 客户端只提交会话 ID,从不提交文件路径。

共享的内容寻址附件($DSH_HOME/attachments)不会被回收:它们可能被其他会话引用,孤儿附件清理不在本插件职责内。

安装

方式一:bundle 激活(本仓库自带脚本)

1. 把整个仓库复制到 ~/.dsh/profiles/web/node_modules/dsh-archive-manager/
2. 运行 node activate.mjs(把 dsh-archive-manager 加入 profile 的 dsh.profile.bundles)
3. 重启 dsh web

方式二:dsh plugin(pnpm)

cd ~/.dsh/profiles/web
pnpm add dsh-archive-manager # 需要包已发布到 npm 或指向 git 仓库
dsh plugin --profile web ...

构建与测试

node build.mjs # 一次构建:src/host/.js → lib/.js,src/client/client.js → lib/client.js
node delete-test.mjs # 删除路径的安全测试(24 项:路径逃逸、符号链接逃逸、缓存清理、损坏容错)

只想重建客户端产物时可以用 node build-client.mjs(等价于 build.mjs 的后半段)。

其它验证:node host-test.mjs、node loader-test.mjs、node _precheck.mjs、node title-test.mjs

title-test.mjs 是标题解析的离线测试(vm + 桩服务,不需要启动 dsh),覆盖批量接口命中、单条失败回退原始日志、无 sessionQuery 时整批回退、取最新一条 session/title、缓存与并发去重。

修复与新增记录

本仓库基于本地安装的 dsh-archive-manager v0.1.0 维护(lib/ 与 src/ 已同步):

| # | 问题 | 修复 |
| 1 | /api/archive-manager/list 返回 500 domain 'workspace' is already open——重复打开官方已占用的存储域 | _domain() 先 storageDomain.get("workspace") 复用已打开的域,取不到才 open |
| 2 | 点「恢复」后会话不立即回到工作区列表,必须刷新页面 | unarchive 改走 registry.setState,同步 registry 内存态,触发 host/archived-sessions-changed |
| 3 | 归档列表只显示「会话+随机后缀」,看不到真实标题 | list 接口通过 sessionPersistence.inspect 读取会话日志中的 session/title 事件,返回 items: [{sessionId, title}];客户端优先渲染真实标题 |
| 4 | 归档后历史归档面板不立即显示新记录(要刷新页面) | 面板每次打开时重新拉取 list |
| 5 | 面板保持打开时归档/恢复不刷新 | 面板打开状态下每 3 秒轮询(DOM 移除后自动停止) |
| 6 | DSH 升级到 0.1.5 后归档列表又只剩「未取到标题 · id 尾号」:官方已删除 sessionPersistence.inspect(),旧调用静默返回 undefined | list 改走当前官方读法 sessionQuery.readTitleSnapshots(ids)(一次列出全部、折叠最新 session/title),并保留 sessionPersistence.open(id, 'read') + 日志折叠作为回退;标题按会话缓存(无标题的 30 秒后重试),避免面板轮询反复解压会话日志 |
| 7 | 官方没有物理删除入口:会话被归档后只能一直躺在磁盘上,占空间且无法清理 | v0.2.0 新增「删除」:二次确认 + 四步清理管线(归档标记 → 工作区记录 → 存储目录 → 投影缓存),realpath 校验路径、拒绝逃逸符号链接、仅允许删除已归档会话;纯逻辑抽到 src/host/paths.js 并由 delete-test.mjs 覆盖 |
| 8 | 面板打开时,某些侧栏按钮"点了没反应":必须随便点一条会话才能恢复。原因是关闭逻辑按名字枚举兄弟面板(只认 ssh / taskboard 的激活事件与 data-dsh--active 属性);任何别的面板——其它第三方面板,或不发该自定义事件的官方面板(如内置的「插件」「技能中心」)——都不会触发关闭。而面板打开时给主区域其他子元素上了 display:none !important,于是那些按钮看起来是死的 | v0.2.1 改为「凡不是我,一律让路」:删掉枚举式名单,改用一个点击外部判定——只要点击不在本插件自己的入口([data-dsh-archive-entry])或本插件自己的面板([data-dsh-archive-view])之内就关闭。不认识任何插件名、不依赖任何自定义事件,因此对所有插件组合都成立;原有的 dsh-panel-activate 事件契约与 ssh/taskboard 互斥逻辑全部保留 |

常见问题

面板打开时,点其它面板图标「没反应」,必须点一条会话才恢复

影响版本: v0.2.0 及更早 | 已修复: v0.2.1

现象: 点开「历史归档」后,直接点侧栏的其它面板图标(「插件」「技能中心」,或其它第三方面板插件的图标)没有反应,切换不过去;随便点一条会话行之后,那些图标就又正常了。

谁会遇到:

| 你的面板组合 | 会不会踩到 |
| 只装本插件 | 不会 |
| 装了 dsh-ssh | 不会 |
| 装了 dsh-task-board(任务看板) | 不会 |
| 装了别的第三方面板插件 | 会 |
| 用官方的「插件」/「技能中心」面板 | 会 |

原因: v0.2.0 的关闭逻辑是按名字枚举兄弟面板的——只认 ssh 和 taskboard 的激活事件与 data-dsh--active 属性。它是照着当年的插件环境写的,任何不同的面板组合都不在名单里;而官方那两个面板并不发 dsh-panel-activate 这个自定义事件,所以单纯往名单里加名字也修不好。

面板打开时会把主区域其它子元素设为 display:none !important 并盖上一层自己的面板,于是那些按钮背后的面板其实已经切过去了,只是内容被藏着——看起来就像"点了没反应"。

v0.2.1 的修法: 从「枚举兄弟」改成「凡不是我,一律让路」——只要点击不在本插件自己的入口([data-dsh-archive-entry])或自己的面板([data-dsh-archive-view])之内就关闭。不认识任何插件名、不依赖任何自定义事件,因此对所有插件组合都成立;原有的 dsh-panel-activate 事件契约与 ssh/taskboard 互斥逻辑全部保留。

临时规避(未升级): 面板打开时,先随便点一条会话行,再点你要去的面板图标。

相关 issue:#1 | 相关提交:1bdd207

反馈与贡献

欢迎提 issue 或 PR:

- 发现 bug(包括上面的修复是否有副作用)、想要新功能,直接开 issue
- 本仓库已包含完整修复;如果插件原作者发布了新版本,欢迎把本仓库的修复合并过去
- 安装/使用问题也可以在 issue 里提问

许可

MIT。上游为本地安装的 dsh-archive-manager v0.1.0(MIT),原作者未在包内署名;本仓库为修复维护版(当前 v0.2.1)。
预览分享卡片长什么样 ↓LianbinZhou/dsh-archive-manager
分享 · DeepSeek Harness Hub
LianbinZhou/dsh-archive-manager

列出已归档会话,一键恢复或彻底删除

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