dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
给 DeepSeek Harness 的跨会话长期记忆:按工作区隔离、跨会话共享。
DSH 的会话一关,上下文就散了。这个插件把「做过什么、当时怎么决定的、那个坑是什么」沉淀下来, 需要时再分层、按需地取回 —— 会话开始时只注入一行提示(告诉模型「你有记忆、去查」),用哪层取哪层。
核心是分层检索:想先看「大概有什么」就调 memory_index 拿目录(浅层要点,置顶在最前);条目很长时,中间还有一层 段级引索 —— 先定位到「哪一段」,只把那一段取回来;深层原文(当时的思考、命令、结论) 留在本地向量库里,真要细节时才取。实测一条 1.5 万字的记忆取回一段约 1200 字,省 92%~97%。
以下命令都在你的命令行终端(PowerShell / CMD / bash 等)中运行。
# ① 前置要求:确认 Node.js 已安装
node -v
# ② 安装 DSH 启动器与 pnpm(已装可跳过;装完请重新打开终端)
npm install -g @deepseek-ai/dsh pnpm
dsh --version # 验证 dsh 命令可用
# ③ 安装本插件:--profile 填你实际在用的那个
# 桌面应用(Electron 版)→ desktop;dsh web → web
dsh plugin --profile desktop add dsh-workspace-recollection装完重开桌面应用(dsh web 用户则重启 dsh web)才生效 —— 运行中的进程持有内存里的旧插件。 之后点会话头部的「🧠 记忆」(或设置页里的「记忆」)就能打开记忆面板。
兼容性:本插件在 dsh `0.2.0-rc.2` 下开发并测试(dsh --version 可查看你的版本)。建议使用相同版本;其他版本如遇问题欢迎反馈。
本插件是纯 JavaScript、无构建步骤 —— clone 下来就能直接挂载:
git clone https://github.com/reaxlll/dsh-workspace-recollection.git
dsh plugin --profile desktop add file:/path/to/dsh-workspace-recollection改源码后重开应用即可(宿主半侧改动必须重启;浏览器半侧刷新页面即可)。
语义检索需要一个本地嵌入模型(Xenova/bge-small-zh-v1.5,512 维,ONNX 量化版约 23MB)。 不用你动手:插件启动时会检查它是否在位,缺了就自己从镜像(hf-mirror.com,失败自动回退 huggingface.co)逐个文件拉下来,下完自动加载 —— 实测 24MB 约 18 秒。 之后运行时零联网,也不调任何云端 API。
网络不通或下载失败也不会影响使用:memory_recall 自动退回关键词检索,其他功能照常; 面板状态条上的「加载模型」可随时重试。
欢迎提 issue 与 PR —— bug、体验问题、优化想法都行。 可以随意对插件进行修改因为作者很可能没空维护,也对应了dsh那句,想要啥自己做(*^_^*) 但动手前请先翻一眼 提交记录, 确认不是已经修掉的同款问题。
所以「main 上已经修了」不等于「你装的这版已经包含」—— latest 没更新时,以 main 的提交记录为准。
dsh 版本、操作系统、复现步骤(有日志或截图更好)——直接用 issue 模板 会把这些列好。
约束行为要用代码而不是提示词)、常见改动配方,以及一份提 PR 前的检查清单。
cwd 分桶 —— 一个工作区一份,这个工作区里的所有会话共享同一份记忆memory_index,按需)→ 段级引索(定位到段)→ 深层原文(按需取整条),三层都有工具,够用就停memory_recall 的结果自动过四路信号(绝对底线 / 硬特征 / 相对长尾 / 平局),跑题的别冒充「找到了」;被筛掉的永不静默丢弃,折成一行 id + 分数 留着你复查bge-small-zh-v1.5(512 维,ONNX q8)跑在本机,不调任何 API;加载约 35ms、编码约 2ms/条ctx.llm.stream 把长正文压成一句话要点(也可自己给,或手写)/api/session-memory/page,也能用系统浏览器开成独立窗口(拖到第二块屏)git remote + 包名 作强指纹,顶层条目名作弱指纹),新旧匹配且旧路径已不存在时自动认领,连带搬向量、重写 Memory.md;判不准就不搬(宁可视而不见,也不把两个工作区的记忆混在一起)systemPrompt.section / tools.register / webServer.register),前后端走 /api/session-memory/*层 · 位置 · 角色 事实源 · $DSH_HOME/session-memory/memory.json · 每条 = text(深层原文,可长)+ summary(浅层一句话) 索引 · /Memory.md · 工作区里的镜像文件,写的是浅层要点;默认只注入一行提示(面板可切 hint / index / full / off);底部「手记」区块原样保留 向量 · $DSH_HOME/session-memory/vectors.json · 每条两份向量:s(浅层,检索定位)/ d(深层,兜底补齐) 段索引 · 不落盘,运行时现算(lib/segments.js) · 把正文按 ## 切成段,用纯 CPU 的 BM25 关键词定位到段号
memory_recall("那个编码的坑") → 浅层向量检索 → 噪音过滤 → 只回「要点 + id + 相似度」
↓ 条目很长时(清单里标了「长文」,上万字的会话导入都是)
memory_locate({ id, query: "zstd" }) → 段级 BM25 定位 → 只回「段号 + 小标题 + 字数 + 命中词 + 预览」
↓ 挑好段号
memory_detail({ ids, segments: [2, 7] }) → 只取这两段切片浅层文本短、语义集中,定位更准也更省;深层只在真要细节时才付 token; 段级引索让「真要细节」也不必整条取回 —— 一个字都不多花。 浅层向量缺失时自动回退;嵌入不可用再回退关键词检索。
取用顺序(写进了工具描述与每次工具返回的末尾):按需逐层、够用就停 —— ① memory_recall 拿要点 → ② 要点不够就用要点里的关键词 memory_locate 拿段号、memory_detail({segments}) 取切片 → ③ 浅层和切片都还不够(要当时的思考、命令全文)才 memory_detail({ids}) 整条兜底。别为了「完整」把三层都调一遍。
工具 · 作用 memory_write · 写记忆:text 写完整深层内容,summary 可选(省略则 AI 总结) memory_index · 目录:置顶在前、其余按更新时间,带分页 / 标签 / 期望词过滤 —— 不知道库里有什么时先调它 memory_recall · 浅层语义检索(带时间通道),只回要点 + id;自带噪音过滤(filter / min_score 可覆盖;被略去的折成一行 id+分数) memory_locate · 段级引索:在已命中的那条里按关键词定位段号,只回段号/小标题/字数/命中词/预览 memory_detail · 按 id 取深层完整原文;给 segments: [段号] 就只取那几段切片 memory_search · 关键词精确检索(不走向量) memory_forget · 删除(两份向量一并清;锁定条目删不掉) memory_import · 把本工作区的历史会话导入长期记忆(一条用户消息 = 一条记忆) session_read · 读历史会话原文/摘要的正门(不带 id 列会话;mode:"raw" 取指定轮次原文含思考)
手工解压$DSH_HOME/sessions/下的 zstd 会话文件已被插件在工具层硬拦(tools/pre-executedeny)——
不是靠提示词自律,而是那条路走不通。要走正门用 session_read。时间戳在磁盘上一直是 ISO(UTC),给人看的那一层统一走 lib/localtime.js:
memory.json 时读一次本机时区(IANA id,例如 Asia/Shanghai),存进 memory.json 的 timeZone 字段。之后一直用它 —— 不会因为换时区设置就让历史时间整体漂移。
UTC+08:00,永远有一个可用值。session_read 的会话列表与轮次时间、记忆条目日期(memory_detail / memory_search)、时间通道(memory_recall 里问「九天前」)、Memory.md 的「更新于」。
POST /api/session-memory/status 的 timeZone(stored / detected / label)。为什么不在每次显示时现算:一条三个月前的记忆该按当时的钟点显示。
这条也是被实测教训出来的 —— 旧代码用 toISOString().slice(11, 16) 显示时间,结果本地 19:40 在界面上变成 11:40,差整整八小时。
要解决的问题:memory_recall 原本是「排序后取 top-K」,没有任何过滤。 导入大批历史会话后,无关条目照样挤进前几名 —— bge 的相似度整体偏高(无关也能到 0.5+), 绝对阈值不可信;而单一分数也切不干净:实测同主题查询里,无关条目的分甚至高于相关条目。
做法(lib/noise.js,纯函数零依赖,可脱离 DSH 单测)—— 四路信号,从硬到软:
信号 · 判定 · 为什么 ① 绝对底线 · `top1 0.08 → 略去 · 砍掉明显跟不上的尾巴 ④ 平局 · 前四条挤在 0.05 跨度内 → 只留前三条 · 没有谁真正胜出时,后面的往往正是跑题的
两条反误杀的护栏(都是实测踩出来的):
会把「配置某个工具」这种跑题条目留下来。
(它讲了这件事,只是没写那个词)。另外硬特征命中的条目豁免③的长尾规则(主题对上号了,分低一点也留)。
可覆盖:memory_recall({ filter: "auto" | "off" | "strict", min_score: 0.5 }) —— off 回到旧行为看原始排名,min_score 是显式下限(一律生效、不豁免)。 阈值集中在 lib/noise.js 顶部常量,调完跑 npm test(用例里的分数直接取自回放,是「阈值有没有被改坏」的回归网)。
深层的排序分是正文分块取最大值得来的 —— 块越多,撞上一个高分块的期望越高, 于是长文天然占便宜,无区分度的查询也会被刷出虚高的分(实测里三条八竿子打不着的长文霸占了前三名)。
所以深层分现在要有资格才被承认,三条任意一条成立即可:
第 3 条的门槛是文档频率(词出现在多少条记忆里):中文 bigram 的泛词毫无区分度 (实测「什么」出现在 98 条里、「10」122 条、「日」60 条),拿「命中 ≥2 词」当判据时 8 条跑题条目里 7 条都能蒙混过关;限到「至少一个词的文档频率 ≤ 15」之后,跑题组 0/8 通过、 真相关组 6/6 通过。三条都不成立的条目会被降级(退回要点分排序,结果里标「要点弱命中」)—— 它不会被丢弃,但不再用正文撞出来的高分冒充相关。
要解决的问题:memory_recall 只能定位到「哪一条」,memory_detail 一给就是整条正文(上限 2 万字)—— 记忆越长取一次越贵,而里面常常只有一小段是真正要找的。
做法(全部本地 CPU:零依赖、零联网、零模型、确定性;代码在 lib/segments.js,可脱离 DSH 单测):
步骤 · 做法 切段 · ## 小节是唯一可靠的语义边界 → 按标题切「节」;一节短于 900 字就整节成一段,长了才按 600 字在空行/句末断开,每片带 节名 (i/n) 序号;不足 40 字的碎屑节(如一行代码围栏)并入前节,标题写回正文保证信息不丢 抽词 · 中文 bigram + ASCII 词元 —— 不引 jieba;路径、命令、常量名原样保留(MAX_TEXT_CHARS、pnpm、/srv/app/deploy) 打分 · 段级 BM25(k1=1.2, b=0.75);含 _ - . : / 的精确形态词加权 ×2.2(这类词命中率极高,也正是记忆里最常搜的东西) 返回 · 只回段号 / 小标题 / 字数 / 命中词 / 命中位置的上下文片段(围绕命中的词取,不是固定截开头),正文一个字都不回
性能与效果:切段 + 全量定位在几百条量级只要几十毫秒(纯 CPU); 用每条记忆自己的要点当 query 回查,能全部命中到自己的正文段;1.5 万字的记忆取 2 段 ≈ 1200 字。
三个坑(都已处理):
(实测 1.5 万字 → 64 段、段名全丢)。→ 先按标题切节,节内不打散。
(实测覆盖率 81.8% → 91.5%)。另外覆盖率不到 100% 是正常的 —— 段里不含小标题行与 --- 分隔线, 标题信息在段号的 label 里。
font_*.png / click_font / 路径全在中后段),而预览只截前 80 字,于是模型看到的是开头那句闲聊 —— 它据此判断「词碰上了、内容没对上」,直接跳过 memory_detail,白白浪费一次已经成功的定位。 → 预览改成围绕最先出现的命中词取上下文(前 36 / 后 76 字,两端补省略号)。
边界:短条目(不足 900 字)整条就是 1 段,切片对它没意义 —— 直接用 memory_detail 取全文即可。
「九天前我问了什么」「10月6日做了什么」这类问题,向量检索答不了 —— 记忆的各层文本里从来没有时间戳,日期型 query 在向量层没有任何落点(只会靠正文块零散撞词, 把语义相近但时间无关的长文刷上来)。
所以 query 里带日期或相对时间时,直接走结构化过滤:按本地日历日筛出那天的条目,按发生时间列出, 并把涉及到的会话 id 一并给出(顺着 session_read 就能读回那天的对话原文)。认这些形态:
形态 · 例子 绝对日期 · 2026-10-06、2026年10月6日、2026/10/6 月日 · 10月6日、10月6号(晚于今天则算去年) 相对日 · 今天、昨天、前天、大前天 N 天前 · 9天前、九天前、十五天前(中文数字也认)
⚠️ 时间字段的可信度是分来源的:只有助手自己写的条目(source.origin为agent/panel),