dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Real deletion for DSH Web conversations: a session-row menu action and a Workspaces-header batch dialog; removes each session's log directory and projection cache. 给 DSH Web 侧栏补上真正的会话删除:右键菜单删单条、区头批量删多条,删掉日志目录与投影缓存。
English | 中文
dsh-delete-session 给 DSH Web 侧栏补上产品本身没有的破坏性操作:真正删除一条对话。会话行的右键菜单多出一条红色**「删除对话」;「工作区」区头多出一枚红色垃圾桶按钮,点开带逐条勾选、标题过滤与全选的批量**对话框。两个入口都先把会话归档——归档集合是产品自带的持久过滤器,也是唯一能让会话从所有分组视图里消失的东西——再删除 $DSH_HOME/sessions/ 下它的日志目录与投影缓存里的对应条目,最后把该行从客户端会话列表里摘掉。最后这一步是必要的:任何被打开过的对话,其对象会一直留在 Host 进程里直到进程重启,而 Host 的会话列表会持续把它报回来。正在运行(agent 跑着)的对话会被拒绝,而不是照删。
作为 bundle 装进 DSH profile,然后重启提供 Web GUI 的那个进程:
dsh plugin --profile web add github:NIGHT576/dsh-delete-session
让它加载的 profile patch 就在本仓库里(cordis.patch.yml,由 dsh.bundle.patch 指向),所以 dsh plugin add 就是全部安装步骤:它把包装成 profile 依赖、把包追加进 dsh.profile.bundles,并在最外层应用这份 patch。Host 半边在启动时导入,所以需要重启进程;浏览器半边由页面拉取,下次刷新页面即生效。
如果你早先手工装过一份,先把 profile 里手写的那条 insert 行删掉再添加 bundle——同一个插件 id 被插入两次会让插件被组合两遍。
当部署里堆积的对话必须真正消失时用它:磁盘压力、共用机器、或者必须干净开场的演示。它不是可撤销的归档——归档集合只是把行藏起来的机制,日志目录是彻底删掉的。如果对话需要可恢复,请用产品自带的归档能力,别装这个插件。
| 入口 | 位置 | 行为 |
|---|---|---|
| 删除对话 | 会话行的 … 右键菜单 | 二次确认后删除这一条 |
| 批量删除 | 「工作区」区头里,搜索 / 视图选项 / 添加工作区 那一排中的垃圾桶按钮 | 打开对话框,按最近更新倒序列出所有普通对话,逐条勾选 |
两者打开的是同一类对话框:标题、×、写明是哪个对话的说明,以及页脚的取消(默认获得焦点)与红色确认按钮。Escape 关闭,点遮罩关闭;删除进行中所有控件禁用,对话框无法被重复进入。
workspaceRegistry.archiveSession,客户端 uiWorkspace.archiveSession,后者还会把完整集合回显进客户端模型)。这一步必须在日志还在时完成:注册表会拒绝既非 live、又不在头部索引、也不在最新持久化列表里的 id。$DSH_HOME/sessions/<项目目录>/<sessionId>/,递归删除、重试三次,然后复查,因此被吞掉的失败不会被当成成功上报。$DSH_HOME/storages/session_projcache/sessions/<sessionId>.json。它只是派生状态,留着也无害,但仍然删掉。sessions.handleSessionRemoved(id),这正是 Host 自己的 api-session/removed 事件在客户端的中继实现;被删的对话恰好是当前查看的那条时,再补一次 sessions.clear()。其他东西一律不动:附件、别的会话、Workspace 记录,以及该会话在 Workspace 记账里占的那个 slot 都原样保留。
agents.get(id).status === 'running',客户端则把该行置灰并跳过。删掉一个正在被追加写入的日志,会让它带着残缺历史被重新创建出来。sessions.clear() 兜底,于是你落到「没有会话」的空状态。插件目前没有声明截图;在 package.json 旁放 screenshots.json(1–8 个相对图片路径)即可添加,市场在未声明时会退回到从本 README 抽取图片。
两半边都是无需构建的朴素 JavaScript:Host 半边是导出 apply/inject 的 Node ESM,浏览器半边是 window.__ModuleLoader__.load({ id, factory }) 形态的模块,React 由外壳注入。本包在运行时不 import 任何 harness 的东西——Host 半边只用 Node 内置模块,浏览器半边只用页面已经发布的服务。
浏览器碰不到文件系统,所以客户端通过一条私有 HTTP 路由调用 Host 半边:POST /dsh-delete-session。路由由每进程随机生成的 48 位十六进制 token 守卫,该 token 经 webServer.tapIndex 以 <meta name="dsh-delete-session-token"> 注入每份渲染出的 index.html,客户端再用 x-dsh-delete-session-token 头回传。跨源页面读不到这个 meta,自定义头又会强制一次外部源无法满足的预检。方法、token、JSON 结构都在碰任何东西之前校验,请求体上限 256 KiB。
刻意保留两套请求契约:
| 请求体 | 响应 |
|---|---|
{ sessionId, currentSessionId, allowCurrent? } | 一个扁平结果对象 |
{ sessionIds: [...], currentSessionId, allowCurrent } | { ok, deleted, failed, results },每个 id 一条结果,最多 200 条 |
扁平契约出现得更早,至今仍然提供,所以客户端遇到批量升级之前的 Host 半边时会退化成逐条请求(识别方法是它回 400/404/405)。每条结果都带稳定的 code——ok、invalid-id、current、running、root-missing、log-locked、empty、bad-body、forbidden、method、internal——本地化文案由客户端负责,Host 不需要知道页面语言。
侧栏在这两个位置都没有提供增量 slot,所以两个入口都由注册在 shell.overlay 里的同一个座位组件注入到产品自己的 DOM 上。
… 按钮所在行的 session id,它是从该行的 React fiber 里读出来的(memoizedProps.node.id,以 session- 开头——这个前缀同时也把 Workspace 行排除掉)。随后 MutationObserver 通过「同时含 Fork 与 Archive 两个来自实时 workspace 词典的标签」来确认新弹出的菜单,收敛到仍同时含有这两个标签的最小子树,再把红色条目追加进去。*_headerActions 那组图标之前。那组容器的上限是 60px——正好两个 28px 图标按钮——并且 overflow: hidden,塞第三个会被裁掉。定位靠 CSS module 类名的后缀(_sectionHeader、_headerActions),因为前缀 hash 每次上游构建都会重生成;主路径以任意渲染出的 role="treeitem" 行为锚点,不对侧栏之外的文档结构做任何假设,页面上还没有会话行时则退化成按区头自己的文案(两种语言拼写都带上)匹配。React 用自己持有的 DOM 引用做增删,不会移除自己不认识、也不属于它的兄弟节点,所以插在两个 React 子节点之间的节点是安全的;万一某次重渲染把它带走,观察者的下一批变更就会补回来。座位被卸载时,它拥有的每个节点、监听器与样式表都会被移除。
detachSession。 Workspace 注册表的归档集合设计上保留会话的记账 slot——"归档的会话保留它的 sessionIds slot,取消归档时能回到原位"——而且它本来就会在会话头部不再可解析时把 slot 过滤掉。早期版本在删完日志后又调了 detachSession,结果会话从它的 Workspace 里掉了出去,用户刚删掉的那一行以「未分组」的形式重新出现。现在什么都不摘。api-session/removed)只由 session/disposed 触发,插件够不到。调用 sessions.handleSessionRemoved(id) 用的就是同一条中继,这正是"Host 仍把对话留在内存里、行却立刻消失"的原因;归档集合则仍是列表刷新后用来对账的持久过滤器。归档先尝试,失败不算致命:删不掉的日志返回 log-locked 并在 detail 里带上目录;由于归档可能已经把行藏起来了,客户端会追加"该行已从侧栏隐藏,但日志文件未能删除",而不是假装删除成功。<DSH_HOME>/sessions 根目录缺失会在碰任何东西之前以 root-missing 失败关闭。日志本就不存在的对话以 ok + alreadyGone: true 上报,这让重试保持幂等。
无。两半边都是界面与文件系统管道:插件不注册工具、不贡献提示词段落或上下文、不写入会话事件,因此它做的任何事都不会进入模型请求。被删的对话是通过侧栏自己的 DOM 按 id 定位的,插件从不读取它的内容。
无。插件从不触碰请求前缀或事件流,因此不可能让提供方的缓存前缀失效。
_sectionHeader、_headerActions)是刻意划出的稳定性边界——前缀 hash 每次上游构建都会变——但超出它的标记改动需要跟进更新插件。两个零依赖的冒烟测试覆盖两半边,且不需要启动 DSH:
npm test
# 也可单独跑
node test/host-smoke.mjs # 在临时 $DSH_HOME 上驱动真实路由
node test/client-render.mjs # 用最小 React 与 DOM 桩渲染真实座位组件
Host 测试同时是上面那两条规则的回归守卫:它断言归档发生在日志被删之前,并断言 detachSession 从未被调用。客户端测试用一个手写的小 DOM 驱动挂载副作用,因此区头注入、点击开框的接线、幂等重注入、文案兜底都被检查到。
改动这里之前值得记住的坑:
*_headerActions。 它上限 60px 且 overflow: hidden,第三个子节点会被裁掉。apply 正常完成、slots.register 也返回了 disposer,但组件永远不出现。深浅色因此改成"打开对话框时采样一次",而不是实时跟随。%VAR%。 cmd.exe 里 echo %DSH_HOME% 会原样输出字符串;请像 Host 半边那样直接读 process.env.DSH_HOME,回退到 <home>/.dsh。service.ctx.emit(...) 会被拒绝;插件只能用自己 apply(ctx) 的那个 ctx。Profile 机制:patch 层能热更,模块代码不能。改 cordis.patch.yml 立即生效,改 lib/index.js 需要重启进程,改 lib/client.js 在下次刷新页面时生效。
运行时不变式: 所有副作用都属于那一个 shell.overlay 座位:注入的菜单项、区头按钮、样式表、对话框与文档监听器都在它的挂载 effect 里创建、由它的 cleanup 移除,因此卸载插件会让侧栏回到它来之前的样子。