dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Codex-style archived-chats manager for DeepSeek Harness: search, filter, unarchive and permanently delete archived sessions. | DSH 已归档会话管理器
DeepSeek Harness(DSH)的已归档会话管理器:一个类似 Codex「Archived chats」的设置页,用来集中查看、搜索、筛选、取消归档,以及永久删除已归档的会话。
Archived-chats manager for DeepSeek Harness — a Codex-style page to search, filter, unarchive and permanently delete archived sessions.
DSH 的归档本身只是一个注册表级的会话 id 集合(workspace 存储域 v2 的 archivedSessionIds):
dsh-session-persistence 的 README 写得很明确:"No deletion or retention API";dsh-session-persistence-jsonl:"Nothing deletes session files";dsh-session-projection-cache:"No eviction or retention surface"。全仓库唯一的 _deleteSession 只是 SQLite 搜索索引的对账逻辑。本插件补上这两块:
| 能力 | 实现位置 |
|---|---|
| 归档会话的富列表(标题 / 项目 / 时间 / 占用 / 是否在运行) | 宿主 GET /dsh-archive-manager/archived |
| 取消归档 | 宿主 POST /dsh-archive-manager/unarchive |
| 单个 / 批量 / 全部永久删除 | 宿主 POST /dsh-archive-manager/delete[-all] |
| 「已归档的聊天」设置页(搜索 + 范围 + 项目筛选 + 分组 + 操作) | 客户端 settings.section(id archived-sessions) |
设置外壳本来就为 id 为
archived-sessions的 section 预留了归档图标 (dsh-client-ui-settings-general的navIcon()),本插件正是注册到那个 id 上。
lib/ 与 client/ 的构建产物已随仓库提交,所以从 git 安装无需先构建:
dsh plugin --profile desktop add github:YOGEMOW/dsh-archive-manager
安装后打开 设置 → 已归档的聊天。
# 1. 构建
npm install
npm run build
# 2. 装入 profile(写入 profile 的 package.json 与 cordis.patch.yml)
dsh plugin --profile desktop add "E:\Git\repositoris\dsh-archive-manager"
本机已按此路径装好(profile 依赖为
dsh-archive-manager: link:E:/Git/repositoris/dsh-archive-manager),并在
~/.dsh/profiles/desktop/cordis.patch.yml 写入了默认配置:
- id: dsh-archive-manager
name: "dsh-archive-manager"
config:
skipRunningSessions: true # 只跳过真正在跑的会话
maxDeleteBatch: 500
卸载:plugin_manager 的 remove_bundle(或 dsh plugin --profile desktop remove dsh-archive-manager)。
package.json、装依赖)需要重启 DSH 进程;lib/index.js 的改动同样只在下一次启动时加载——运行中的进程会把已加载的
ESM 模块缓存住,禁用/启用 bundle 也只会重新挂载 fiber,不会重新 import;client/client.js 重新构建后,已打开的页面由 dsh-client-hmr 的产物轮询热更新。「删除」是不可恢复的物理删除。每一步都对应一个真实踩过的坑:
agents.get(id).status === 'running' 加上
workspace/session-activity waterfall(turn/job/subagent/schedule,与
Workspace.archiveSession 的准入检查同源)。仅仅"已加载在内存里但空闲"的会话
可以删除——这正是之前被误判成"运行中"而删不掉的那种。unarchiveSession 再删文件,等于当场把行放回侧边栏,这就是"删除后
又出现在工作区"的直接原因。会话仍驻留内存时,归档记录保留为墓碑,工作区继续隐藏它。api-session/removed。浏览器的会话列表是按连接世代拉取一次的快照,
文件消失不会产生任何帧;@deepseek-ai/dsh-api-remotes 会把该事件转发给每个已连接的
客户端,客户端把该行从列表快照里移除——这是唯一能让"已经打开着的窗口"立刻更新的通道。<DSH_HOME>/sessions/<projectKey(cwd)>/<encoded-id>/(经
sessionPersistence.locate() 定位,缺失时按 DSH 的目录布局回退计算,最后才扫描兜底)。storageDomain.get('session_projcache').table('sessions').delete(id)),
而不是裸 rm 文件:裸删只会删掉文件,内存里的行还在,下一次 checkpoint 会把文件写回来;
存储域未挂载时才回退到直接删文件。:memory: + openAt: 'never',且从不喂给侧边栏)。删除不会触碰全局附件对象存储(~/.dsh/attachments):那是内容寻址的共享对象,其他会话可能仍在引用同一份对象。
删除某个"仍驻留内存"的会话后,归档集合里会留下一条没有内容的记录——它是工作区继续隐藏 该行的依据。页面底部会把它们单列成「已删除的残留记录」:
cordis.patch.yml 的 config)- id: dsh-archive-manager
name: 'dsh-archive-manager'
config:
skipRunningSessions: true # 默认 true:跳过 turn/job/subagent/schedule 仍在活动的会话
maxDeleteBatch: 500 # 单次请求最多删除多少个会话
(skipLiveSessions 是旧键名,仍兼容,但语义已从"跳过已加载"改为"跳过在运行"。)
设置 → 插件 里的「已安装」行,标题/描述/图标不是从 dsh 字段读的,而是 DSH 启动装配时的
包级展示元数据(dsh-app-boot 的 readPluginMeta):
| 显示项 | 来源 | 缺省回退 |
|---|---|---|
| 标题 | locale/<lang>.json 的 meta.title | package.json 的 name |
| 描述 | locale/<lang>.json 的 meta.description | package.json 的 description |
| 图标 | package.json 顶层 icon 字段,指向包内相对路径 | 通用拼图占位图 |
两个硬性条件:
locale/en.json 必须存在——目录扫描以英文资源为锚点,缺了它整个 locale/ 都不会被读;
其余语言文件名必须是合法语言 id(zh.json、zh-CN.json…),与英文文件同目录。exports 里暴露,否则 Node 解析器按包出口直接判定"不存在":
"./locale/*.json": "./locale/*.json"。图标的约束(不满足会让该包的回退到占位图或报一条元数据诊断):包内相对路径、
SVG/PNG/JPEG/WebP、不超过 256 KiB、realpath 后仍在包目录内。DSH 会把图标读成
data: URL 内联给页面,所以图标不需要额外路由。
本仓库已按此配置:icon.svg + locale/{en,zh}.json + exports 里的 locale 通配。
npm run verify:metadata 会按 DSH 的解析方式从 profile 目录实际解析一遍
(<pkg>/package.json、<pkg>/locale/en.json、<pkg>/locale/zh.json),并复核上述所有图标规则。
src/index.ts 宿主半:HTTP 路由 + 删除/清理编排
src/shared/model.ts 两端共享的纯逻辑(路径编码、筛选、分组、格式化)
src/client/index.ts 客户端半:注册 settings.section、词典、会话列表刷新
src/client/ArchiveManagerSection.tsx 页面本体
src/client/ArchiveManager.module.css 页面样式(全部走 dsw 主题 token)
icon.svg 插件页图标
locale/en.json locale/zh.json 插件页标题与描述
scripts/verify-display-metadata.mjs 按 DSH 的方式校验展示元数据
tests/model.test.mjs 纯逻辑单元测试
tests/delete.test.mjs 真实文件系统上的删除/清理集成测试
tests/render.test.mjs jsdom 渲染测试
宿主半不 import 任何 @deepseek-ai/* 运行时包:第三方插件的依赖由 profile 解析,
而 DSH 自身包由应用提供。宿主只用 Node 内建模块与 cordis 上下文,因此不存在解析失败风险。
npm run typecheck # 宿主 + 客户端两个 TS 工程
npm run build # tsc -> lib/ ;tsdown -> client/client.js
npm test # 纯逻辑 + 宿主删除集成测试(先构建宿主半)
npm run test:render # jsdom 渲染测试(先打包测试壳)
npm run verify:metadata # 展示元数据(插件页标题/描述/图标)
npm run test:all # 全部
三类测试各自的职责:
| 套件 | 覆盖 |
|---|---|
tests/model.test.mjs | 路径编码(对齐 DSH 的 projectKey/encodeSegment)、搜索/范围/项目筛选、分组、排序、格式化、残留判定 |
tests/delete.test.mjs | 真实文件系统上的删除/清理编排:临时 DSH_HOME 造会话目录与投影缓存 → 断言目录与缓存被删、api-session/removed 被广播、归档记录在"会话未驻留内存"时被释放而"仍驻留"时保留为墓碑、运行中/有 job 的会话被跳过、经存储域删缓存、locate() 缺失时回退目录布局、清理只放行无日志且未驻留的记录 |
tests/render.test.mjs | jsdom 中挂载真实页面组件:分组/行/徽标(运行中 vs 已加载)/字节渲染、残留区与主列表分离、运行中行删除按钮禁用、搜索过滤、取消归档请求、删除确认(未勾选确认前不得发请求)、全部删除只针对有内容的行、挂载时调用会话列表刷新 |
渲染测试把 @deepseek-ai/dsh-client-ui-primitives 别名到 tests/render/primitives-stub.tsx:
真实 primitives 只能在浏览器的加载器模块表里运行(它的 ESM 入口 import 十几个 CSS 模块和
markdown/高亮依赖)。桩件同名同 props、同无障碍结构,因此测的是页面自身的行为
(状态机、宿主调用、菜单、确认门控)。真实组件的 props 契约由 npm run typecheck 对着
npm 上 0.2.0-rc.2 的 .d.ts 校验。
客户端产物必须是经典脚本,且以
window.__ModuleLoader__.load({ id: "dsh-archive-manager", factory: (require) => {
开头(scripts/normalize-client-banner.mjs 负责把 rolldown 折行后的 banner 折回一行);
require 只能解析加载器模块表里的种子(react、react/jsx-runtime、
@deepseek-ai/dsh-client-ui-primitives),其余一律内联。
:memory: 且 openAt: 'never',删除后无需维护它;若将来换成持久索引,
它会在下一次对账时自行丢弃消失的行。