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

会话管家 TOBYCAI/dsh-sessions-manager

DeepSeek Harnessv3.7.10
TOBYCAI7 天前有提交已提供中文文档扫描:含敏感能力

可能是目前最稳定、体验最好的 DSH 会话管理插件。

49.1/100
综合分
36
GitHub 星标
1,404
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-sessions-manager
npm 源 · dsh-sessions-manager 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · market
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-sessions-manager @ 3.7.10
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/31 22:55:49

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

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

读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 189 处,展示前 103 条)
  • 读取本地文件
    lib/index.js:266
    return normalizeStarStore(JSON.parse(readFileSync(indexPath, "utf8")));
  • 写入本地文件
    lib/index.js:272
    await mkdir(dir, { recursive: true });
  • 写入本地文件
    lib/index.js:274
    await writeFile(tmp, JSON.stringify(normalizeStarStore(store), null, 2), { encoding: "utf8", mode: 384 });
  • 写入本地文件
    lib/index.js:275
    await rename(tmp, indexPath);
  • 读取本地文件
    lib/index.js:388
    return normalizeTagStore(JSON.parse(readFileSync2(indexPath, "utf8")));
  • 写入本地文件
    lib/index.js:394
    await mkdir2(dir, { recursive: true });
  • 写入本地文件
    lib/index.js:396
    await writeFile2(tmp, JSON.stringify(normalizeTagStore(store), null, 2), { encoding: "utf8", mode: 384 });
  • 写入本地文件
    lib/index.js:397
    await fsRename(tmp, indexPath);
  • 读取本地文件
    lib/index.js:585
    return normalizeFilterStore(JSON.parse(readFileSync3(indexPath, "utf8")));
  • 读取本地文件
    lib/index.js:687
    return normalizeStore(JSON.parse(readFileSync4(indexPath, "utf8")));
  • 读取本地文件
    lib/index.js:797
    return normalizeStore2(JSON.parse(readFileSync5(indexPath, "utf8")));
  • 读取本地文件
    lib/index.js:855
    entries = await readdir(dir);
  • 删除本地文件
    lib/index.js:871
    await rm(path, { force: true });
  • 删除本地文件递归
    lib/index.js:1761
    await rm2(candidate, { recursive: true, force: true });
  • 删除本地文件递归
    lib/index.js:1771
    await rm2(candidate, { recursive: true, force: true });
  • 删除本地文件递归
    lib/index.js:1790
    await rm2(sessionDir, { recursive: true, force: true });
  • 删除本地文件递归
    lib/index.js:1865
    await rm2(artifacts.sessionDir, { recursive: true, force: true });
  • 删除本地文件递归
    lib/index.js:1990
    await rm2(deriveSessionDir(artifacts.root, canonical, sid), { recursive: true, force: true });
  • 发起网络请求
    lib/client.js:642
    const res = await fetch(path, {
  • 写入剪贴板
    lib/client.js:1186
    await navigator.clipboard.writeText(full);
  • 写入剪贴板
    lib/client.js:1233
    navigator.clipboard.writeText(id).then(markCopied).catch(viaExecCommand);
  • 发起网络请求
    lib/client.js:1298
    const res = await fetch("/archived-sessions/export-md?sessionId=" + encodeURIComponent(it.sessionId));
  • 发起网络请求
    lib/client.js:1319
    fetch("/api/session.export?sessionId=probe&includeDescendants=false", { method: "HEAD" }).then((res) => setZipOk(res.status !== 501)).catch(() => setZ
  • 读取本地文件
    src/auto-archive.js:113
    return normalizeAutoArchiveStore(JSON.parse(readFileSync(indexPath, 'utf8')))
  • 写入本地文件
    src/auto-archive.js:120
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/auto-archive.js:122
    await writeFile(tmp, JSON.stringify(normalizeAutoArchiveStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/auto-archive.js:123
    await rename(tmp, indexPath)
  • 读取本地文件
    src/empty-scan-index.js:62
    return normalizeEmptyScanIndex(JSON.parse(await readFile(path, 'utf8')))
  • 写入本地文件
    src/empty-scan-index.js:93
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    src/empty-scan-index.js:95
    await writeFile(tmp, JSON.stringify({ schemaVersion: EMPTY_SCAN_SCHEMA_VERSION, entries: next }), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/empty-scan-index.js:96
    await rename(tmp, path)
  • 写入本地文件
    src/empty-scan-index.js:110
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    src/empty-scan-index.js:112
    await writeFile(tmp, JSON.stringify({ schemaVersion: EMPTY_SCAN_SCHEMA_VERSION, entries: store }), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/empty-scan-index.js:113
    await rename(tmp, path)
  • 读取本地文件
    src/handle-era-ops.js:59
    try { projects = await readdir(root, { withFileTypes: true }) } catch (e) { return { removed: 0, superseded: [], real: [] } }
  • 读取本地文件
    src/handle-era-ops.js:71
    const st = await stat(candidate)
  • 读取本地文件
    src/handle-era-ops.js:73
    entries = await readdir(candidate)
  • 删除本地文件递归
    src/handle-era-ops.js:77
    try { await rm(candidate, { recursive: true, force: true }); removed++ } catch (e) { }
  • 删除本地文件递归
    src/handle-era-ops.js:85
    await rm(candidate, { recursive: true, force: true })
  • 读取本地文件
    src/handle-era-ops.js:104
    try { entries = await readdir(sessionDir) } catch (e) { return { cleaned: true, leftover: [] } }
  • 删除本地文件递归
    src/handle-era-ops.js:106
    await rm(sessionDir, { recursive: true, force: true })
  • 删除本地文件递归
    src/handle-era-ops.js:211
    await rm(artifacts.sessionDir, { recursive: true, force: true })
  • 读取本地文件
    src/handle-era-ops.js:232
    const original = await readFile(backupLogPath)
  • 写入本地文件
    src/handle-era-ops.js:242
    await mkdir(targetDir, { recursive: true })
  • 写入本地文件
    src/handle-era-ops.js:244
    await writeFile(staged, rewritten, { mode: 0o600 })
  • 写入本地文件
    src/handle-era-ops.js:245
    await rename(staged, join(targetDir, artifacts.generationFiles[0]))
  • 读取本地文件
    src/handle-era-ops.js:289
    try { sourceBytes = (await stat(artifacts.logPath)).size } catch (e) { sourceBytes = null }
  • 写入本地文件
    src/handle-era-ops.js:339
    await mkdir(backupRoot, { recursive: true })
  • 写入本地文件
    src/handle-era-ops.js:341
    await rename(artifacts.sessionDir, backupDir)
  • 删除本地文件递归
    src/handle-era-ops.js:373
    try { await rm(deriveSessionDir(artifacts.root, canonical, sid), { recursive: true, force: true }) } catch (_) {}
  • 写入本地文件
    src/handle-era-ops.js:374
    try { await mkdir(dirname(artifacts.sessionDir), { recursive: true }) } catch (_) {}
  • 读取本地文件
    src/handle-era-paths.js:117
    const st = await stat(sessionDir)
  • 读取本地文件
    src/handle-era-paths.js:119
    entries = await readdir(sessionDir)
  • 读取本地文件
    src/index.js:568
    const st = await stat(loc.path)
  • 读取本地文件
    src/index.js:603
    try { return normalizeTrashStore(JSON.parse(readFileSync(TRASH_INDEX, 'utf8'))) } catch (e) { return normalizeTrashStore(null) }
  • 写入本地文件
    src/index.js:607
    await mkdir(TRASH_DIR, { recursive: true })
  • 写入本地文件
    src/index.js:609
    await writeFile(tmp, JSON.stringify(normalizeTrashStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/index.js:610
    await rename(tmp, TRASH_INDEX)
  • 读取本地文件
    src/index.js:706
    : (removedPath ? await stat(removedPath).then((st) => ({ mtimeMs: st.mtimeMs, size: st.size })) : null)
  • 读取本地文件
    src/index.js:858
    const existsOnDisk = await stat(originalPath).then(() => true).catch(() => false)
  • 删除本地文件
    src/index.js:1000
    try { await unlink(target) } catch (e) { if (e && e.code !== 'ENOENT') throw new Error('删除文件失败:' + String((e && e.message) || e)) }
  • 读取本地文件
    src/index.js:1066
    try { canonical = await realpath(p) } catch (e) { canonical = null }
  • 写入本地文件
    src/index.js:1068
    await mkdir(p, { recursive: true })
  • 读取本地文件
    src/index.js:1069
    canonical = await realpath(p)
  • 写入本地文件
    src/index.js:1143
    await mkdir(dirname(newPath), { recursive: true })
  • 写入本地文件
    src/index.js:1150
    await rename(oldPath, backupPath)
  • 删除本地文件
    src/index.js:1163
    await unlink(backupPath)
  • 删除本地文件
    src/index.js:1167
    try { await unlink(stagedPath) } catch (_) {}
  • 删除本地文件
    src/index.js:1168
    if (destinationInstalled) { try { await unlink(newPath) } catch (_) {} }
  • 删除本地文件递归
    src/index.js:1196
    try { await rm(dir, { recursive: true, force: true }) } catch (e) { return entries }
  • 删除本地文件
    src/index.js:1269
    if (backupPath) { try { await unlink(backupPath) } catch (e) {} }
  • 读取本地文件
    src/move-notices.js:83
    return normalizeStore(JSON.parse(readFileSync(indexPath, 'utf8')))
  • 写入本地文件
    src/move-notices.js:90
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/move-notices.js:92
    await writeFile(tmp, JSON.stringify(normalizeStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/move-notices.js:93
    await rename(tmp, indexPath)
  • 读取本地文件
    src/pending-moves.js:65
    return normalizeStore(JSON.parse(readFileSync(indexPath, 'utf8')))
  • 写入本地文件
    src/pending-moves.js:72
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/pending-moves.js:74
    await writeFile(tmp, JSON.stringify(normalizeStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/pending-moves.js:75
    await rename(tmp, indexPath)
  • 读取本地文件
    src/saved-filters.js:119
    return normalizeFilterStore(JSON.parse(readFileSync(indexPath, 'utf8')))
  • 写入本地文件
    src/saved-filters.js:126
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/saved-filters.js:128
    await writeFile(tmp, JSON.stringify(normalizeFilterStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/saved-filters.js:129
    await rename(tmp, indexPath)
  • 读取本地文件
    src/star-index.js:57
    return normalizeStarStore(JSON.parse(readFileSync(indexPath, 'utf8')))
  • 写入本地文件
    src/star-index.js:64
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/star-index.js:66
    await writeFile(tmp, JSON.stringify(normalizeStarStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/star-index.js:67
    await rename(tmp, indexPath)
  • 读取本地文件
    src/state-temp-sweep.js:30
    try { entries = await readdir(dir) } catch (e) { continue }
  • 读取本地文件
    src/state-temp-sweep.js:35
    try { info = await stat(path) } catch (e) { continue }
  • 删除本地文件
    src/state-temp-sweep.js:38
    try { await rm(path, { force: true }); removed++ } catch (e) { }
  • 读取本地文件
    src/tag-index.js:137
    return normalizeTagStore(JSON.parse(readFileSync(indexPath, 'utf8')))
  • 写入本地文件
    src/tag-index.js:144
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/tag-index.js:146
    await writeFile(tmp, JSON.stringify(normalizeTagStore(store), null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/tag-index.js:147
    await fsRename(tmp, indexPath)
  • 读取本地文件
    src/title-persist-index.js:86
    return normalizeTitleIndex(JSON.parse(await readFile(path, 'utf8')))
  • 写入本地文件
    src/title-persist-index.js:120
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    src/title-persist-index.js:122
    await writeFile(tmp, JSON.stringify({ schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries: next }), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/title-persist-index.js:123
    await rename(tmp, path)
  • 写入本地文件
    src/title-persist-index.js:137
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    src/title-persist-index.js:139
    await writeFile(tmp, JSON.stringify({ schemaVersion: TITLE_INDEX_SCHEMA_VERSION, entries: store }), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    src/title-persist-index.js:140
    await rename(tmp, path)
  • 读取本地文件
    src/zstd-frame.js:104
    const buf = readFileSync(filePath)
  • 写入本地文件
    src/zstd-frame.js:119
    writeFileSync(filePath, Buffer.concat([newFrame0, rest]))
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-workspace@deepseek-ai/dsh-session-persistence-jsonl@deepseek-ai/dsh-session-query@deepseek-ai/dsh-storage-domain@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-client-ui-settings
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

界面预览

6 张 · 点击查看大图

功能亮点

  • 设置面板与侧边栏双入口
  • 归档恢复跨区移动软删
  • 回收站恢复批量清空
展开完整介绍(248 字)↓

DSH 会话管理插件:设置面板和侧边栏两个入口,支持归档、恢复、跨工作区移动、软删进回收站;回收站可恢复、批量清空、7/30/90 天自动清理,按标题、会话 ID、工作区搜索,带磁盘与血统详情;已适配 DSH 0.1.7 和 0.2.0(v4 日志,旧格式会话也能移动,活跃会话排队移动)。DeepSeek Harness 会话管理器,管归档、恢复、移动、回收站整套生命周期,支持搜索和单会话统计,兼容 0.1.7 和 0.2.0(v4 会话格式)。支持 macOS、Windows、Linux。

v3.7.102026-10-27
· dsh-sessions-manager v3.7.10 批量删除入口补齐:在任何会话视图里,多选之后都能直接移入回收站。 · 中文 · 修复多选后没有删除入口的问题:批量「删除所选」此前只在「已归档」视图渲染,导致「全部 / 活动 / 已收藏 / 空白」视图下多选会话只有「归档所选」「移动所选」,无法像单条会话那样移到回收站。现在各会话视图都提供该按钮;回收站视图仍使用独立列表,不受影响。 · 批量删除行为与单条一致:先移入回收站、可恢复,默认级联子代理会话,并在按钮上补充「移入回收站,可在回收站恢复」说明。 相关:#18 · 来自社区的贡献 感谢 @moon-bamboo 的复测与其提供的会话日志。那份日志经实测可被正确判定为空白会话(只含会话头与权限 / 沙箱 / 审批元数据,没有任何消息),因此本版没有改动空白判定规则;「活动 / 全部」列表仍会列出空白会话的现有行为也保持不变,本版未新增自动清理空白会话的能力。 · English Batch delete is now available in every session view, so selected sessions can always be moved to the recycle bin. · Fixed the missing batch delete entry point: the "delete selected" button was previously rendered only for the archived filter, leaving the all / active / starred / empty views with archive and move actions but no way to move selected sessions to the recycle bin. The button is now offered in every session view; the trash view keeps its separate list and is unaffected. · Batch deletion matches the single-session behavior: sessions go to the recycle bin first and can be restored, subagent sessions are cascaded by default, and the button now documents this in its tooltip. Related: #18 · Community contributions Thanks to @moon-bamboo for retesting and sharing a session log. That log is correctly classified as an empty session (it contains only the session header plus permission / sandbox / approval metadata, with no messages), so the empty-session rules are unchanged in this release. Empty sessions also remain listed under all / active as before, and no automatic cleanup of empty sessions has been added.
v3.7.92026-10-26
· dsh-sessions-manager v3.7.9 侧栏会话列表现在支持拖拽自动滚动:把会话拖到列表上/下边缘会自动滚,视口外的工作区标题也能直接拖过去。 · 中文 · 拖拽自动滚动(PR #19):把会话拖到侧栏列表的上/下边缘就会自动滚动,越靠边越快(每帧最多 20px),到顶/到底自动停;光标停在列表上方一点的侧栏标题一带时按「顶端」处理。只滚侧栏里那个会话列表容器,拖到右侧对话区不会误滚对话内容。 · 滚动后高亮跟随:滚动时浏览器不会补发 dragover,这里补一条合成事件,本插件的目标高亮与 DSH 原生的插入标记都会跟着滚动后的位置走,不会停在原处。 · 只对会话拖拽生效:从系统拖文件进来、拖选文本、拖图片进窗口都不会再让侧栏滚动;从侧栏拖出一条会话时才生效。 · 热更新后依然可用:插件热更新(HMR)会先用新实例再用旧实例收尾,此前的安装守卫会让自动滚动在第一次热更新后永久失效。现在安装改为 token 认领,旧实例只在仍持有该 token 时才摘监听器;插件停用 / 卸载时干净移除。 · 长列表不额外吃 CPU:滚动容器的定位改为先判 role 再算样式(不再对每个祖先做子树查询),侧栏外拖动按间隔重试;没有可滚容器时不保留空转的动画帧循环。 · 矮列表不出怪滚动:感应带限制在容器高度一半以内,很矮的列表(如只有几行)把光标停在正中不会自己滚。 · 来自社区的贡献 / Community contributions · PR #19 由 @Ax697128 提交:自动滚动的主体实现(只滚 [role=tree] 容器、不介入 DSH 落点判定、补合成 dragover)来自该 PR,感谢。合并后在其上做了加固:安装守卫改为 token 认领(原实现会在热更新后失效)、合成 dragover 复用真实拖拽的 dataTransfer(空载荷可能被上游读成「不是拖拽」而清掉插入标记)、容器定位去掉每祖先的子树查询并避开文档里其它树、只对会话拖拽生效。如实说明:滚动步长的数学有单测覆盖,浏览器级的拖拽行为仍靠手工验证,没有自动化用例。 相关:#19 · English · Drag auto-scroll (PR #19): hold a dragged session near the top or bottom edge of the sidebar list and it scrolls automatically, faster the closer you get (20px per frame at most), stopping at either end; parking the cursor slightly above the list, over the sidebar header, counts as the top edge. Only the sidebar's own session list scrolls — dragging over the conversation area never scrolls the chat. · The highlight follows the scroll: the browser does not re-send dragover while the list moves, so a synthetic one is dispatched — both this plugin's drop target highlight and DSH's native insertion marker track the post-scroll position instead of staying behind. · Only session drags count: dragging files in from the OS, a text selection or an image no longer scrolls the sidebar; it only engages for a session dragged out of the sidebar. · Survives hot reload: cordis applies the new module before the old one's cleanup, so the previous install guard made auto-scroll die permanently after the first hot update. Installation is now token-owned — the old instance only remove
v3.7.82026-10-21
· dsh-sessions-manager v3.7.8 子代理会话不再混进顶层列表,删除父会话会连带回收它们的整棵子树;只被插件注入过上下文的会话现在会被正确判为空白。 · 中文 · 子代理不再污染列表(issue #18):血缘分组下子代理会话不再占顶层行、不计入「全部 / 活动 / 已归档 / 空白」的计数,也不参与全选——它们只作为父行的「N 子代理」存在。父会话不在当前列表(已删除 / 已归档 / 被筛掉)的子代理统一收进列表末尾的「未归属的子代理会话」卡片:默认收起不占版面,展开后可逐条打开 / 归档 / 删除,搜索命中的自动展开,一条都不丢。切到平铺视图则保持与 DSH 原生一致。 · 删除父会话连带回收子代理(issue #18):默认把整棵子代理子树一起移入回收站(只读官方 origin / parentSession 血缘字段,复用同一份列表快照,不额外扫盘),删除确认框可取消勾选以保留它们。回收站恢复 / 彻底删除父会话时同样级联,不会留下无父的孤儿;用户自己单独删掉的子代理不会被父会话重新认领,恢复父会话也不会把它带回来。批量删除同样级联,提示会写明连带数量。 · 「▾ N 分支 来源:xxx」组头展开后显示成员(issue #18):此前该组头只切换箭头状态、不渲染任何成员,点了像没反应。现在展开(或搜索命中)时会渲染同一份成员列表。 · 「全选」只选看得见的行(issue #18):此前全选遍历的是过滤后的原始列表,会选中被折叠的子代理、被聚拢的分支成员与合成组头——界面上没有对应勾选框,只能靠「取消全选」撤销。现在只覆盖当前列表里真正占一行、可单独勾选的会话。 · 插件注入的上下文不再冒充内容(issue #18):日志里只有 header、生命周期事件和一条只插入插件 instructions 的 agent/inbox/spliced(例如记忆插件注入的上下文)时,该会话此前永远进不了「空白」分类。现在会看插入项来源:source.kind 以 plugin: 开头或 form 为 'instructions'、且未删除任何消息的插入不算内容;真用户消息与无法识别的载荷照旧算内容。判定只读判定所需的最小字段,不把消息正文读进内存。 · 适配 DSH 0.2.1-rc.2(issue #16):侧栏会话行被上游包进一层 wrapper 后,展开计数的校准此前会整段短路(按钮上没有任何校准标记);折叠分组的成员改用与展开无关的 sessionCount 加按 cwd 统计的回收站条数,按钮文案与实际点开能出现的行数一致。空分组标题的容器定位同步修复:从 wrapper 向上找到真正的分组容器,重新出现可见会话时标题自动回来。 · 来自 issue 的反馈 / Issues addressed · #18 由 @moon-bamboo 报告,五条现象都复现并修复。如实说明未做的部分:平铺视图下子代理仍然逐行并列、可单独勾选(这是与 DSH 原生一致的视图,本版不改动它);血缘视图下子代理不作为独立勾选行出现,只能从父行的「N 子代理」或「未归属的子代理会话」卡片处理;级联删除默认开启但可取消,取消后子代理保留并进入「未归属」卡片。 · #16 的后续三个短路点由 @zcbxxx1 在 0.2.1-rc.2 上实测给出(PR #17),感谢!未做/受限的部分:展开按钮的文案仍由上游渲染,本版是原位改写;回收站与空白会话依旧不显示在侧栏。 修复:#16 #18 · English · Subagents no longer pollute the list (issue #18): under lineage grouping, subagent sessions no longer take top-level rows, no longer count towards All / Active / Archived / Empty, and are excluded from select-all — they exist only as the parent row's "N 子代理". Subagents whose parent is not in the current list (deleted / archived / filtered out) are collected into a single "未归属的子代理会话" card at the end of the list: collapsed by default, expandable to open / archive / delete each of them, auto-expanded when a
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-sessions-manager 可能是目前最稳定、体验最好的 DSH 会话管理插件。 DSH 会话管理器:在设置 → 会话管理里统一归档、移动、恢复、查看详情与血缘(子代理折叠、分支标记、空白清理);在主页侧边栏直接标记未读、移动、删除会话,并把子代理就地折叠进父会话。删除先进入回收站,可恢复或彻底清理。 DSH 持久化插件(host + browser 双半),同时覆盖「设置面板」与「主页侧边栏」两个入口,不用打开设置就能完成高频会话操作。插件同时识别旧式 header 列表/readFrom 与新版 snapshot/SessionHandle,按当前 Runtime 的实际能力启用操作:删除与迁移路径必须可验证才会开放(新版 Runtime 上由守卫式路径推导与写所有权探测保障),无法验证时在界面与 Host 端同步禁用,避免假成功。 功能 设置面板:会话管理 - 统一面板:顶部提供「全部 / 活动 / 已归档」三个常驻视图,低频的「已收藏 / 空白 / 回收站」收进行尾的箭头按钮,点按横向滑出展开(低频视图处于选中态时自动保持展开);支持按标题、会话 ID、工作区搜索,按工作区筛选,并按创建时间或标题排序。结果行合并为一句说明,如「显示 5 个会话,另有 4 个子代理折叠在父会话下」。
展开完整文档(README 剩余 1 节)
dsh-sessions-manager

可能是目前最稳定、体验最好的 DSH 会话管理插件。

DSH 会话管理器:在设置 → 会话管理里统一归档、移动、恢复、查看详情与血缘(子代理折叠、分支标记、空白清理);在主页侧边栏直接标记未读、移动、删除会话,并把子代理就地折叠进父会话。删除先进入回收站,可恢复或彻底清理。

DSH 持久化插件(host + browser 双半),同时覆盖「设置面板」与「主页侧边栏」两个入口,不用打开设置就能完成高频会话操作。插件同时识别旧式 header 列表/readFrom 与新版 snapshot/SessionHandle,按当前 Runtime 的实际能力启用操作:删除与迁移路径必须可验证才会开放(新版 Runtime 上由守卫式路径推导与写所有权探测保障),无法验证时在界面与 Host 端同步禁用,避免假成功。

功能

设置面板:会话管理

- 统一面板:顶部提供「全部 / 活动 / 已归档」三个常驻视图,低频的「已收藏 / 空白 / 回收站」收进行尾的箭头按钮,点按横向滑出展开(低频视图处于选中态时自动保持展开);支持按标题、会话 ID、工作区搜索,按工作区筛选,并按创建时间或标题排序。结果行合并为一句说明,如「显示 5 个会话,另有 4 个子代理折叠在父会话下」。筛选栏下方的维护栏收纳「存储占用」「标签管理」「自动归档」等按需展开的面板级工具,不占用视图位置。
- 收藏(星标):会话行左侧常驻星标按钮,单击即收藏 / 取消(乐观更新、失败回滚);「已收藏」视图与 DSH 的活动 / 归档状态正交、可叠加;收藏索引为插件自有 schema v3,不触碰 DSH 日志,会话被彻底删除时自动清理。
- 标签与保存筛选:给会话贴自定义标签(全局 ≤200、单会话 ≤10;支持重命名、合并、删除——删除标签只删标签,绝不触碰会话),并按标签筛选列表;「视图 + 工作区 + 排序 + 标签」的组合可保存为筛选预设(≤20 条)一键应用。标签与预设存在插件自有索引里(同星标索引纪律),跟随这台机器的 DSH 数据目录、不跟账号走。
- 冷态标题同步:侧栏使用日志中最新的 session/title 修正冷启动缓存,改名后的会话无需先打开即可显示新名称;冷启动大库里标题先留空、后台分批补齐,补齐后已经在屏幕上的侧栏行和「⑂ 分支」标记的悬停说明会跟着自动更新,不必等页面重建。
- 侧栏跨工作区拖拽:直接把会话拖到目标工作区标题即可切换工作区;目标高亮、同工作区拦截、失败反馈,并保留“更多 → 移动会话”作为键盘操作入口。拖到侧栏列表的上/下边缘会自动滚动列表(越靠边越快,到顶到底即停),视口外的工作区标题也能拖到。
- 归档 / 恢复:归档把会话从侧栏隐藏;恢复取消归档并放回原工作区分组。
- 移动到工作区:任选已有工作区或新建目录路径(自动创建),新建目录支持点击 「浏览…」 调用系统目录选择窗口。会话的工作目录与日志一起迁移。旧版 Runtime(0.1.2-rc.1)下已打开的会话也可立即移动;新版 Runtime 为会话日志引入单写者所有权(DSH 打开会话即持有、只在进程退出时释放),此时移动会自动排队而不是失败——释放后由插件自动完成,详见下文「移动活跃会话(排队移动)」。
- 会话详情:展开单条会话查看磁盘占用、轮次 / 步骤 / 用户·助手消息 / 工具调用 / 图片附件统计、工具使用分布、搜索·抓取记录、write/edit 写过的文件列表(已过滤磁盘上已不存在的路径),以及血缘(父会话 / 子会话 / 子代理)。
- 血缘分层披露:面板的「分组」下拉提供血缘(折叠分组,默认)与平铺(全部并列)两种视图。血缘视图下:子代理会话折叠在其父会话下(「▸ N 子代理」);由同一来源会话派生的分支(fork)自动聚拢成组(「▸ N 分支」,组内按创建时间升序,来源父行原位保留;父会话被筛掉/归档/删除时降级为「来源:<短 ID>」组头,一行不丢——因此「活动」视图里已归档父会话的分支会挂在「来源」组头下,这是设计不是 bug)。注意:聚拢在设置面板的血缘视图;侧栏受官方 React 托管行 DOM 限制只打标记不重排。子代理会话同理不占顶层行:血缘视图下它们不计入「全部 / 活动 / 已归档 / 空白」的计数、不参与全选,只作为父行的「N 子代理」存在;父会话不在当前列表(已删除 / 已归档 / 被筛掉)的子代理统一收进列表末尾的「未归属的子代理会话」卡片(默认收起,展开后可逐条打开 / 归档 / 删除,搜索命中自动展开),不会再以「(无标题)」行铺满列表。切到平铺视图则与 DSH 原生一致,子代理逐行并列、可单独勾选。分支会话带绿色「⑂ 分支」标记(悬停显示来源父会话;父标题为后到数据,到达后自动刷新),空白会话带灰色标记并可进入「空白」视图统一查看与清理。空白会话识别采用事件类型法:0.1.3 起日志头部扩容且创建时恒写一帧生命周期元数据,旧体积阈值失效——插件对候选会话做官方解码精判,头部之外没有任何内容事件才判空白(解码失败按非空兜底);插件注入的上下文不算内容——agent/inbox/spliced 只插入 source.kind 以 plugin: 开头或 form: 'instructions' 的消息、且未删除任何消息时,该会话仍判空白(真用户消息与无法识别的载荷照旧算内容)。3.7.0 起精判完全移出数据返回路径:侧栏数据首拍即回,空白与否先按「未知」处理(未知一律不隐藏、不计入空白分类),后台小队列逐条判定、结论落盘跨重启复用,后续拍自动收敛——「⑂ 分支」等零解码信息不再被任何精判解码挡住。
- 导出:详情面板底部提供两个入口——「下载原始日志 (ZIP)」直接走 DSH 官方 session.export 端点(含子会话与附件,持久化后端不支持时自动隐藏);「导出 Markdown」由本插件把会话渲染为人类可读对话记录(front matter + 按轮分节 + 用户 / 助手 / 工具调用摘要,流式增量不重复)。
- 存储占用分析:维护栏的「存储占用」按钮按需展开,按工作区聚合会话日志的磁盘占用(占比条 + 会话数),并列出占用最大的会话 Top 10。纯只读统计,不修改任何数据;默认收起、展开时才统计,因此不会拖慢会话列表的加载。
- 自动归档:可设为把 30 / 60 / 90 天未活跃的会话自动收进「已归档」,默认关闭。当前正在使用的会话与已收藏的会话(该保护可关闭)永不自动归档;只做归档标记,不删除任何数据,随时可恢复。检查在打开面板时惰性触发,每天最多一次,也可点「立即检查」手动执行。
- 批量多选:全选 / 批量归档 / 恢复所选 / 删除所选(批量删除一次二次确认)。全选只覆盖当前列表里真正占一行、可单独勾选的会话——折叠中的子代理、聚拢的分支成员与「来源:」组头都不参与,避免选中界面上看不见的会话;批量删除同样按父会话级联子代理,提示会写明连带数量。勾选后批量操作栏吸顶钉在面板顶部,列表再长也无需滑回顶部操作;另提供「移动所选…」批量跨工作区移动(复用单移动面板:已有工作区 / 新建目录 / 系统目录浏览),单条失败不阻断整批,失败明细逐条回报。

主页侧边栏:会话 ⋯ 菜单增强

- 标记未读:在会话的 ⋯ 菜单顶部新增「标记未读 / 标记已读」切换;也可直接点击会话左侧的状态圆点切换。进入该会话后自动取消未读标记。
- 移动会话:点击后右侧悬浮子菜单列出工作区名称,选择即移动到目标工作区;当前工作区以「当前」标注并置灰。
- 删除会话:删除会先把会话移入回收站(非立即物理删除),设置面板可回收站恢复或彻底清理。

主页侧边栏:子代理折叠与分支标记

- 子代理折叠:DSH 上游侧栏按设计不渲染子代理行,插件按官方血缘字段(origin: 'subagent' / parentSession / delegationDepth)把子代理行就地注入父会话之下——「▸ N 子代理」徽标在该行被选中或悬停时出现,点击展开缩进的子代理行,行内点击即通过官方 API 打开对应子代理会话;子代理被删除后折叠列表同步收敛。
- 分支标记:由父会话分支(fork)产生的会话,行上会在选中或悬停时显示绿色「⑂ 分支」chip,提示其血缘来源。
- 空白会话:0.1.3 上游不把空白会话渲染进侧栏,插件侧同步隐藏并固化为默认行为;空白会话的管理入口收敛到设置面板的「空白」视图。

主页侧边栏:状态圆点

每行会话左侧显示一个小圆点,颜色直接读取 DSH 原生 StateDot 状态,含义如下:

| 颜色 | 状态 | 说明 |
| 🔵 蓝 | 手动标记未读 | 通过 ⋯ 菜单或点击圆点手动标记;进入会话后自动清除 |
| 🟡 黄 | 工作中 | 会话正在运行(新版 ongoing,兼容旧版 running) |
| 🟠 琥珀 | 等待反馈 | 会话有追问,需要用户输入或确认(warning) |
| 🟢 绿 | 完成后未读 | 会话已完成(done)但你还没重新打开看过;看过一次后不再显示 |
| 🔴 红 | 出错 / 需关注 | 会话遇到错误(error) |
| 无圆点 | 完成后已读 / 空闲 | — |

圆点与 DSH 自带状态点接管对齐:插件隐藏 DSH 原生点并按上表重新渲染,避免两个点并存。

回收站

- 回收站是会话管理中的独立视图,显示待清理数量和删除时间。
- 正常删除的会话先进入回收站,不会立即从磁盘移除,也不会被归到「未分组」。
- 回收站内可对单条会话执行恢复(回到原工作区)或彻底删除(物理清理日志)。
- 删除父会话会连带回收它的子代理:默认把整棵子代理子树一起移入回收站(子代理生命周期跟随父会话),删除确认框可取消勾选以保留它们;恢复或彻底删除父会话时,这些连带条目同样级联处理,不会在回收站里留下无父的孤儿。用户自己单独删掉的子代理不会被父会话重新认领(恢复父会话也不会把它带回来)。
- 支持清空回收站一键彻底删除全部内容。
- 支持永久保留或 7 / 30 / 90 天自动清理,并可校验日志完整性。
- 回收站索引采用版本化 schema、串行变更和原子写入;旧版数组索引会自动迁移。
- 已彻底删除的会话会被永久隐藏,不再出现在侧栏与会话管理列表。
- 永久删除会先写入后端墓碑,再等待 live Session 与持久化控制器退出、移除日志并重建工作区索引;即使重连或宿主仍有旧索引,也不会重新出现或生成空的「未分组」。

移动活跃会话(排队移动)

DSH(0.1.5 起)给每个会话日志加了单写者锁:会话一旦被 DSH 打开(包括只是选中当前会话),写所有权就被 agent-loop 持有。官方没有提供任何「关闭会话 / 释放所有权」的入口——该所有权只在 DSH 进程退出时释放(切换会话不释放;实测空闲等待也不会释放)。此时强行搬运是危险的:在写者会继续往旧文件写,并在旧路径重建空壳目录。

因此本插件对「被占用的会话」采用排队延迟移动:

- 命中占用时不会失败,而是登记进待移动队列(~/.dsh/sessions-manager/pending-moves.json),并如实提示「已排队」——绝不会在没有真正搬运的情况下报「已移动」。
- 队列的自动执行时机:插件启动后立即并密集重试(0 / 1 / 3 / 6 / 12 / 30 秒,必须抢在浏览器打开会话之前)、之后每 2 分钟轻量兜底、以及宿主释放会话时。
- 队列最多保留 50 条;同一条连续 5 次因非占用原因失败会被放弃并记录日志(启动 30 秒内的失败不计入,避免启动竞态丢队列)。
- 查看与取消:POST /archived-sessions/pending-moves、POST /archived-sessions/pending-moves/cancel { sessionIds };队列非空时面板显示「待移动队列」小节(逐条取消)。
- 结果不再静默:后台完成或最终放弃(连续 5 次非占用失败)会落成通知,下次打开 DSH 页面时以提示弹出(放弃类附原因)。通知在服务端保留到确认展示(最多 20 条、最长 7 天),没弹就不会丢。
- 要真正搬走一个活跃会话:先发起移动(进队列),再重启 DSH 并先别打开那个会话——启动后几秒内会自动完成。未完成的项跨重启保留,不会丢。

关于会话格式 v3 / v4(DSH 0.1.5+ / 0.1.7+)

DSH 0.1.5 把会话日志格式升级到 v3,0.1.7 再升到 v4,均由 runtime 自己在读取时完成迁移:

- 迁移是自动的,且保留原文件——所以同一个会话目录里可能同时出现 session.v2.jsonl.zstd 与更高代的 session.vN.jsonl.zstd,属官方预期行为;本插件始终按最高代读取,与官方一致。
- 迁移单向、不支持降级读取:升级并打开过会话之后,把 runtime 回滚到旧版本会导致那些会话无法读取。回滚前请先备份 ~/.dsh/sessions。
- 官方只迁移受支持的旧日志;遇到未知事件或损坏数据会拒绝迁移且不做任何修复。这类会话在插件里会表现为读取/移动报 SessionFormatUnsupportedMigrationError / SessionFormatError——插件无法代修,只能如实报错(可先确认该会话是否确实异常,再决定保留或删除)。
- 0.1.7 起新建会话严格要求 v4 头部:本插件自 v3.7.2 起在跨工作区移动时自动按当前后端的格式重新采样会话头——旧格式(v3 及更早)会话可以正常移动,且移动后的副本直接落在当前格式(已用真实 v3 旧日志端到端实测)。更早的插件版本在 0.1.7 上移动旧格式会话会失败。
- 0.2.0 仍是 v4:当前格式版本由插件从后端的格式目录包动态探测(不写死版本号),所以 0.1.7 / 0.2.0 共用同一套处理。

截图

展开查看截图(侧边栏菜单 / 侧栏子代理折叠 / 设置面板 / 批量移动 / 空白视图 / 自动归档 / 存储占用 / 已收藏 / 回收站 / 会话详情)

主页侧边栏 ⋯ 菜单(标记未读、移动会话、删除会话)

主页侧边栏子代理折叠(「▸ N 子代理」徽标 + 就地展开)与「⑂ 分支」标记

设置面板「会话管理」(血缘分组 / 空白视图 / 筛选收纳)

批量移动面板与吸顶批量操作栏

空白会话视图(事件类型法识别)

自动归档面板(维护栏内联展开)

存储占用分析(维护栏内联展开)

已收藏(星标)视图

回收站

会话详情(磁盘占用 / 统计 / 工具使用)

安装

方式一:Git 依赖直装(推荐,无需本地 clone,重启 DSH 生效)
dsh plugin --profile desktop add "github:TOBYCAI/dsh-sessions-manager"

web 端(若你也用 dsh web):
dsh plugin --profile web add "github:TOBYCAI/dsh-sessions-manager"

方式二:本地 link(开发调试)
git clone https://github.com/TOBYCAI/dsh-sessions-manager.git
dsh plugin --profile desktop add link:/path/to/dsh-sessions-manager

装完重启 DSH(或刷新页面重新加载 bundle)后,设置 → 会话管理 / 主页侧栏 ⋯ 菜单 即可用。

卸载

dsh plugin --profile desktop remove dsh-sessions-manager
dsh plugin --profile web remove dsh-sessions-manager

结构

package.json npm 元数据 + dsh.bundle.patch + dsh.client(浏览器半注册) cordis.patch.yml 向 profile bundle 插入本插件行 src/index.js host 源码(/archived-sessions/* JSON 路由) src/client/index.jsx client 源码(React,settings.section + 侧栏 DOM 增强) src/auto-archive.js 自动归档设置(schema v4)+ 候选判定纯函数 src/storage-stats.js 存储占用聚合(纯函数,按工作区 / Top N) src/lineage.js 血缘分类与空白会话判定(纯函数,事件类型法) src/empty-scan-index.js 空白精判结论的磁盘缓存(跨重启复用,指纹 + TTL 双闸门) build.mjs esbuild 构建脚本(本地开发时生成 lib/) lib/index.js 预构建 host(ESM) lib/client.js 预构建 client(ModuleLoader CJS handshake)

lib/ 已预构建,clone 下来即可直接用、无需 esbuild。若要改源码,运行 npm i -D esbuild && npm run build 重新生成 lib/;提交前用 npm run check:dist 重建并与仓库产物比对(CI 跑同一道检查),改版本号后务必一并重建——构建指纹可复现为 vX.Y.Z+源码哈希,正是为了让这道比对精确可靠。

Host 路由

| 方法 | 路径 | 说明 |
| POST | /archived-sessions/sessions | 列出全部会话(含已归档标记),供「会话管理」面板 |
| POST | /archived-sessions/list | 列出归档会话(跳过已删除/不存在者) |
| POST | /archived-sessions/archive | 归档(隐藏)单个会话 |
| POST | /archived-sessions/archive-many | 批量归档 |
| POST | /archived-sessions/restore | 取消归档单个 |
| POST | /archived-sessions/restore-many | 批量取消归档 |
| POST | /archived-sessions/delete | 删除单个会话——移入回收站(非立即物理删除);默认连带整棵子代理子树(cascadeSubagents: false 可保留),返回 cascaded / cascadedIds |
| POST | /archived-sessions/delete-many | 批量移入回收站(同样按父会话级联子代理,返回 cascaded 总数) |
| POST | /archived-sessions/trash/list | 列出回收站中的会话 |
| POST | /archived-sessions/trash/restore | 从回收站恢复会话(连带恢复当初跟随它一起回收的子代理,返回 cascaded) |
| POST | /archived-sessions/trash/purge | 彻底删除回收站中的单个会话(连带条目同样级联,返回 cascaded) |
| POST | /archived-sessions/trash/purge-many | 清空/批量彻底删除回收站会话(按父会话级联) |
| POST | /archived-sessions/trash/settings | 读取或更新自动清理策略 { retentionDays } |
| POST | /archived-sessions/trash/verify | 校验回收站日志是否仍存在 |
| POST | /archived-sessions/workspaces | 列出可选目标工作区 |
| POST | /archived-sessions/move | 把会话移动到目标工作区 { sessionId, targetPath };目标会话正被 DSH 打开时返回 { queued: true } 并登记排队 |
| POST | /archived-sessions/move-many | 批量跨工作区移动 { sessionIds, targetPath }——单条失败不阻断,失败明细逐条回报,被占用的会话进 queued 列表,末尾统一重建索引 |
| POST | /archived-sessions/pending-moves | 查看待移动队列(会话被占用时排队,释放后自动完成) |
| POST | /archived-sessions/pending-moves/cancel | 取消排队 { sessionIds } |
| POST | /archived-sessions/pending-moves/notices/ack | 确认排队移动终局通知(展示后清理;未确认项最长保留 7 天) |
| POST | /archived-sessions/details | 会话详情(磁盘/统计/工具/fetch/文件/血缘){ sessionId } |
| POST | /archived-sessions/sidebar-state | 返回侧栏权威标题、回收站 ID、永久删除墓碑、血缘分层(子代理 / 分支 / 空白标记)、warmPending / refinePending(标题预热、空白精判两条后台队列是否仍在途)与 moveNotices(排队移动终局通知,空则省略字段) |
| POST | /archived-sessions/lineage-tree | 递归子代理树(面板血缘分组与侧栏折叠的数据源),已过滤回收站与墓碑 |
| POST | /archived-sessions/star/set | 收藏 / 取消收藏 { sessionId 或 sessionIds, starred } |
| POST | /archived-sessions/tags/list | 标签定义 + 会话→标签映射 { tags, assignments } |
| POST | /archived-sessions/tags/create | 新建标签 { name }(重名/超 200 拒绝,错误带 code) |
| POST | /archived-sessions/tags/rename | 重命名 { id, name }(id 不变) |
| POST | /archived-sessions/tags/merge | 并入 { fromId, toId }(去重合并后源标签消失) |
| POST | /archived-sessions/tags/delete | 删除标签 { id }(只删定义与映射,不碰会话) |
| POST | /archived-sessions/tags/set | 全量设置某会话的标签 { sessionId, tagIds } |
| POST | /archived-sessions/filters/list / save / delete | 筛选预设 { name, filters }(≤20;filters 为不透明载荷) |
| GET | /archived-sessions/export-md?sessionId= | 单会话 Markdown 导出(人类可读对话记录) |
| POST | /archived-sessions/storage | 存储占用聚合(按工作区排行 + 最大的会话){ topN } |
| POST | /archived-sessions/auto-archive/settings | 读取或更新自动归档策略 { inactiveDays, skipStarred };读取时惰性触发每日检查 |
| POST | /archived-sessions/auto-archive/run | 立即执行一次自动归档检查(忽略每日节流) |
| POST | /archived-sessions/capabilities | 返回当前持久化代际及读取、归档、回收站、永久删除、跨工作区移动能力 |

删除会话默认进入回收站,只有回收站内的「彻底删除」才会物理移除日志。被彻底删除的会话由前端永久隐藏,避免 DSH 运行时缓存使其重新出现在侧栏或「未分组」中。

兼容性

- 适配系统:跨平台(macOS / Windows / Linux)——只要 DSH 能在该系统运行即可;本插件 host 基于 Node(约 ^22.19 或 >=24)、浏览器端为 React,不依赖特定操作系统 API。
- DSH Desktop / web 均可(同一套 host + client)。
- peerDependencies 见 package.json;react、@deepseek-ai/ 由 DSH 运行时提供。
- 0.1.2-rc.1:现有读取、归档、回收站、永久删除和跨工作区移动能力保持可用。
- 0.1.3-alpha.1:支持 snapshot 列表和 SessionHandle 只读流程;永久删除与跨工作区移动经由守卫式路径推导 + 写所有权探测开放(见下文),无法安全验证时自动禁用,其余管理能力不受影响。能力以面板实际提示为准。
- 0.1.5-rc.1 / 0.1.5-rc.2(已实测):会话日志格式升级到 v3,本插件已适配——按最高代读取(与官方一致)、v0/v2/v3 多代共存、跟随 SessionHandle.read() 的新返回结构
({ eventState, events })、跨工作区移动按整目录搬运并自动回收被取代的旧代副本;被 DSH 打开着的会话改为排队延迟移动(见上文「移动活跃会话」)。
- 0.1.7-rc.2(已实测):会话格式再升 v4,格式版本目录拆分到独立包;本插件已适配——读取走官方自动迁移,跨工作区移动自动按后端当前格式重采样会话头(旧格式会话可正常移动,落盘即当前格式)。
- 0.2.0-rc.1 / 0.2.0-rc.2(均已实测):
- 会话格式仍为 v4(版本由后端格式目录包动态探测,不写死)。
- ctx.sessions 已退化为会话数据流服务(不再提供 open):打开会话改走官方 uiWorkspace.openSession——设置面板与侧栏的「打开会话」、子代理导航均已适配(issue #12)。
- 安装门禁:0.2.0 会校验插件 peerDependencies 中的 @deepseek-ai/dsh 版本范围,本插件已放宽为 >=0.1.0-rc.6,0.1.7-rc.2 与 0.2.0-rc.2 通过(issue #13)。
- 存储域(ctx.storageDomain)在 0.2.0 是领域数据形式:写入经 schema 校验并在返回前落盘。本插件只写官方域 schema 内的字段,已用真实域 + 真实 WorkspaceRegistry 端到端实测(归档 / 恢复 / 列表 / 存储统计 / 跨工作区移动),并回到磁盘介质核验归档状态真的持久化。
- 活跃会话判定:0.1.7 起 host 侧不再有「当前会话」访问器(三个历史探测全部落空),改由客户端每 15 秒上报当前会话 id、host 只在 45 秒新鲜期内采信;用于「移动正在看的会话」(409 / 排队)与「自动归档跳过当前会话」两条保护。心跳停止(面板关闭、标签页隐藏、进程退出)即视为无法证明活跃,放行移动而不是把会话永久锁死。
- 0.2.1-rc.2(已实测,issue #16):
- 会话格式仍为 v4;@deepseek-ai/dsh-session-persistence-jsonl 不再默认导出类,改为 cordis 插件({ name, apply })——本插件两种形态都能挂载,真栈冒烟按双形态各跑一遍。
- 侧栏把会话行与工作区行包进一层 wrapper(span):工作区标题行与分组容器的定位改为「先看直接子节点,找不到再向下找且不跨嵌套子分组」,否则整段校准会静默短路。
- 折叠分组的成员数改由与展开无关的 sessionCount 校准(上游把 collapsedSessionRows 改名 watermarkRows,且 group.sessions 折叠时为空、展开时经 sessionVisible() 过滤);任一输入缺失就保留上游原文案,不猜数字。
- 空分组标题收起:分组容器仍是无 role 的 groupSection(role="group" 自 0.1.7 起只出现在子分组上),锚点改为工作区标题行的父容器。
- 未经验证的未来 Runtime 默认只开放能够识别的安全能力;插件不会用方法存在与否冒充行为兼容。

新版 Runtime(SessionHandle 世代,0.1.3+)的行为差异与降级说明
- 回收站恢复需要校验:恢复前会确认底层会话仍然存在(live / stat / 列表三级判定)。底层会话已不存在(DSM_SESSION_MISSING)、已被彻底删除(DSM_SESSION_PURGED),或索引仍在但日志文件消失(DSM_SESSION_LOG_MISSING)时,返回准确错误;无法核验日志位置时如实标注 unverified,不会假装校验通过。恢复到「工作区已删除」的会话会成功,并提示其暂归「未分组」。
- 彻底删除 / 清空回收站 / 自动物理清理:官方公共契约未提供删除 API,插件沿用 legacy 时代的半官方路线——从后端实例的存储根目录字段出发做三层守卫式路径推导(根目录 → 会话目录结构 → 会话 ID 归属校验),通过后整目录删除,并以官方 stat 复核;存在活跃写入的会话会被拒绝(409)。推导失败(如无法确认存储根目录)时自动禁用并说明原因,绝不盲删。
- 跨工作区移动:主路径为官方 create + append 事件重放(写所有权探测会拒绝被 DSH 占用写权限的会话、revision 前后校验防并发写入、备份回滚保证失败不留半移动状态);后端存在同 ID 幽灵记录时回退到 frame0 cwd 改写搬运(帧数与内容逐位校验)。路径同样来自守卫式推导;移动后自动重建工作区分组索引,无需重启。Runtime 0.1.5 起会话日志会多代共存(官方发布新代时保留旧代,如 session.jsonl.zstd + session.v2.jsonl.zstd + session.v3.jsonl.zstd),而官方按「同一 ID 只能落在一个项目目录」校验——因此移动是整目录搬运:先把源会话目录整体移开(对官方扫描彻底不可见),目标侧重放校验成功后再删除源目录;顺带自动回收其它项目目录里被取代的旧代副本(历史移动残留),避免同一会话永久卡在「跨目录重复」而无法移动或删除。
- 彻底删除墓碑不压制新会话:若同 ID 会话被重新创建,墓碑自动让位,新会话正常出现在列表与侧栏。
- 元数据缓存:判断会话日志有没有变化,新 Runtime 用官方快照的 revision(仅同一进程内可比,绝不写进跨重启的缓存文件),旧 Runtime 用文件 (mtime, size) 指纹。列表的目录 / 创建时间直接来自快照会话头,不需要读日志;标题也不在页面加载时现读——缓存里没有就先留空显示,后台分批补齐后自动刷出。读取失败与「会话确实没有标题」严格区分:失败不写入任何缓存,稍后自动重试(该会话日志一变就立刻再试),偶发一次失败不会让会话永久显示「(无标题)」。
- 自动归档:新 Runtime 不提供可靠的「最后活跃时间」,无法证明会话闲置时检查会跳过(no-activity-data),绝不基于猜测归档。
- 侧栏注入:DOM / React fiber 识别收敛为可版本化的 adapter;上游侧栏结构不被识别时整体安全停用,不影响官方侧栏本身。识别正常时以 MutationObserver 增量驱动为主,仅保留低频兜底检查。
- 血缘分层:子代理会话以官方头部字段识别(origin: 'subagent'、parentSession、delegationDepth);上游侧栏不渲染子代理行与空白会话,插件据此把子代理行就地注入父会话下、把空白行隐藏。空白判定用事件类型法——0.1.3 创建会话时恒写一帧生命周期元数据(permission/preset / sandbox/mode / approval/policy),体积阈值法失效;插件对压缩体积 ≤8KB 的候选走官方解码精判,头部之外无内容事件才判空白,解码失败按非空兜底。结果按会话 + 跨重启稳定的体积指纹缓存并落盘(只有真解码成功的结论才持久化),日志没变就不重复读;精判在独立的后台小队列进行(批间让出事件循环),侧栏数据的返回始终零解码。

License

MIT © TOBYCAI
预览分享卡片长什么样 ↓TOBYCAI/dsh-sessions-manager
分享 · DeepSeek Harness Hub
TOBYCAI/dsh-sessions-manager

可能是目前最稳定、体验最好的 DSH 会话管理插件。

回到顶部