dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
跨压缩记忆:压缩时把被压掉的内容存到本机,提问时只注入相关的那几段(没有就一点不注入),让模型重新想起你早先定过的事。
跨压缩记忆插件:在同一个超长会话里被上下文压缩过几次之后,你早先问过 / 定过的事,模型先看到它,再作为参考结合这次的新条件综合回答——不会当新问题从零重来。
只做同一个会话内、跨压缩这一段。跨会话记忆不在范围内(那是另一类插件的事)。 插件自包含:不依赖任何其他记忆插件,不访问互联网,默认一次模型都不调用。
关于本插件(请先读这两句)
- 本插件由 AI 协助开发与维护,可能存在缺陷。请在重要场景自行验证后再依赖它;发现问题欢迎开 issue 或直接改(见下方「许可」)。
- 它主要面向学习与测试用途,作者不保证持续更新。代码是 MIT 许可,fork、修改、再发布都可以。
- 运行环境需要带 zstd 支持的 Node:插件读会话日志要用
node:zlib的zstdDecompressSync,所以package.json的engines声明了node >= 22.15。CI 实测 Node 20.20.2 会在加载时直接报语法错(does not provide an export named 'zstdDecompressSync'),Node 24.x 正常,22.x 由 CI 矩阵验证。DSH 本体读会话日志用的是同一个 API,所以下限一致。
一句话:上下文压缩会把早先的对话从模型眼前拿走(原文仍在磁盘上),这个插件在被拿走的那一刻把它存到你本机,之后只在相关的时候把那几段递回给模型。
一个真实场景:
它不做什么:
写这一节是因为:它现在确实做不到"像人一样"地回忆。 下面每条都是实测出来的边界,不是谦虚话。
它只有"被压缩掉"的那部分内容。 会话里还没被压缩的内容本来就在模型眼前,插件不管、也不需要管。实测:本会话 162 条提问里,只有 71 条(43.8%) 曾经落在压缩范围内——其余仍在窗口里。所以用得越久(压缩次数越多),它覆盖的比例越高;刚聊不久时它帮不上什么忙,是正常的。
它按"词"找,不按"意思"找。 你问的用词和当初聊的用词对不上,它就可能找不到。我们试过加本地的语义模型(把内容变成向量再比):结果并没有更好(在真实语料上还不如按词找),还多占几百 MB 磁盘——所以没上。换成"另一种说法"仍然要靠可选的辅助大模型(第 7 节)。
"最早 / 第一次 / 上次 / 刚刚"这类话,它现在会认,也会按三类去偏。
0.2.6 起,这类时间指代词会被识别出来;加权只改排序,不改"谁能被注入"(该不该注入只由词法分 + 证据门决定)。
指代词分三类、各配一种时间形状:刚刚 / 刚才 → 越新越高;最早 / 第一次 / 最初 → 越早越高;
之前 / 上次 / 原来 → 中间偏新最高(钟形,波峰贴近当前,两端只是更低、不是归零)。
一条消息里同时命中两类(例如"我们刚刚说的那个最早提过的设想")→ 这次不做任何时间重排:
这句话真正要的是"两头"(最早一遍 + 刚刚一遍,现在问的是第三遍),而自动检索这条路上没有模型可用,
猜不出来 —— 所以口径是"宁可不用,也不用错"(固定优先级必然选错一边)。
幅度有上限(默认 0.7×–1.35×,可在设置文件里调 timeBoostMin / timeBoostMax 等七个键;
both_ends 的"中间凹陷深度"是新增的 timeBothEndsDip,默认 0.2)。
"两头"由点 ✕ 那条路负责:辅助模型判一个"时间意图"
(earliest / recent / both_ends / none),选 both_ends 时用两端高、中间低的形状重排;
这个意图只重排、不改资格,也不新增任何模型调用(第 7 节)。
意图从哪来:优先用「改写」那一次调用顺带输出的字段;改写被跳过时(本地已强命中),
由「分页判读」那一次本来就要发生的调用顺带判——所以"本地强命中、但可能选错"这种点 ✕ 的主场景
也拿得到意图,调用次数一次都不多(回执里 timeIntent.via 会写明来源是 rewrite 还是 pageread)。
但它不会自己想"这题该去哪一段时间翻"。那一步得由模型(也就是跟你对话的那个我)先想清楚,再去用它。
它不能替你想,也不能替你发消息。 它最多把几段原文递到模型眼前。平台限制:插件不能往会话里插一条消息、也不能让模型"立刻重答"——所以点 ✕ 找到的东西会排进你下一轮(第 4 节)。
它不保证找得到,但会明说。 找不到时会直接显示「未搜索到强相关内容」,不会硬凑一段出来。没命中就一分 token 都不花。
5.5 它只能定位到"块",定位不到"块里哪一轮"。 库里一个块往往装了好几轮问答(按字符数切,目标 900、上限 1500 字符)。所以它给你的是"这几轮里有过这段话",而不是"第 N 轮说的"。 实测代价:同一次压缩写进去的所有块,时间戳完全相同(真库里 223 块只有 5 个不同的时间值)、也没有轮次字段 → 时间排序只能按块在文件里的前后近似(跨压缩事件是准的,同一次压缩内部是近似的)。 要更细的序,得在入库时给每条块写轮次或块内偏移,那需要重建历史库——本轮没做(代价大于收益)。
5.6 在 Linux/macOS 上,如果工作目录正好在插件目录里,工具结果会被静默跳过。
入库时有一条防自我膨胀的规则:工具结果如果读的是插件自己的源码,就整条丢掉(否则插件会把自己的代码当记忆存起来、越滚越大)。这条规则用当前平台的原生路径语义判断"是不是在插件目录里"。
后果(Windows 上因为盘符天然在外部,遇不到):在 POSIX 上如果你的 cwd(或被测路径)落在插件目录内部,真实的工作文件也会被判成"插件自己的源码"→ 那条工具结果不入库,而唯一痕迹是诊断日志里的 selfSourceSkipped 计数。
触发条件很窄:把插件仓库本身(或它的子目录)当工作区时才会踩到;正常把项目放在别处不受影响。本轮没改运行时判据(它对真实路径是对的),只记在这里。
| ① 本地检索(默认) | ② 大模型辅助检索(可选) | |
|---|---|---|
| 什么时候用 | 每次提问都做 | 只有你在面板 ⑦ 里选了档位才启用 |
| 怎么找 | 本地词法打分:中文字符二元组 + 英文词,标题/关键词/正文分权重(BM25),过阈值才算命中 | ①压缩入库时让模型给每块补几个「你以后可能怎么问这块内容」的短词;②你点 ✕ 时让模型把你的问法改写成关键词,并判定哪一条真的强相关 |
| 花什么 | 0 模型调用、0 token;未命中一分 token 都不花(连注入文本都不产生) | 按次计费,走 DSH 的模型服务;压缩时每批一次调用,点一次 ✕ 一次调用(相同提问 30 天内不重复调用) |
| 延迟 | 提问热路径零延迟 | 压缩时的调用你在等压缩,感觉不到;点 ✕ 时最多等 8 秒(界面会显示「检索中」) |
| 命中率 | 词对得上就命中;「换个说法问」容易漏 | 明显更高(尤其是口语化、同义改写的问法) |
| 怎么切换 | 默认就是它 | 面板 ⑦「使用方式」三选一:不调用大模型(默认) / 调用主模型 / 调用指定模型 |
两条路可以同时存在,但提问时的检索永远是本地的:模型只在「压缩入库扩写」和「你点 ✕」这两个时机被调用,绝不进提问热路径。模型改出来的词也要自己过本地阈值——它没法凭一句话把不相关的内容塞进你的上下文。
| # | 时机 | 做什么 | 成本 |
|---|---|---|---|
| ① | 压缩时 | 把这次被压掉的内容本地入库:一层是 DSH 已经写好的摘要,按小节切开存(短、准、结论级);另一层是被压掉那段的对话原文(你问的 + 助手答的文字),外加模型读过的文件 / 检索结果原文(read / grep / glob / web_fetch / history_read)。 | 0 模型调用(选了模型档位才会给这些块补关键词) |
| ② | 压缩后 | 注入一份目录级「本会话此前脉络」(话题 — 结论),让新窗口一开场就知道过去聊过哪几大块。挑不出值得留的就一点都不注入。 | ≤ 300 token / 次压缩,自适应,可为 0 |
| ③ | 提问时 | 先在本地比一次关联性,命中才注入参考。 | 命中 ≤ 700 token / 轮(≤2 条、每条 ≤300 字符、每条至少 50 字符才值得注入);未命中 0 token |
几个刻意的取舍:
history_read 工具(只在用户明确要求时调用)。会话里每条助手回答下面都有一个 ✕(只在这个会话被压缩过之后才出现;没压缩过就没什么可找的)。点它的完整行为是:
earliest 最早那次 / recent 最近那次 / both_ends 两头、最早与最近都相关而中间次要 / none 与时间无关)。选 both_ends 时,本地候选会按两端高、中间低的形状重排(默认 1.35× / 1.22× / 1.35×)。这只是排序:不改变"哪几条有资格被注入",也不额外多花一次调用(就是这一次调用多一个字段)。
另外两个细节:
单次口径(全部是上限,不是配额):
| 项 | 数字 |
|---|---|
| 压缩后总览 | ≤ 300 token / 次压缩,可为 0(实测一次:190 token / 8 行 / 747 字符) |
| 单轮注入 | ≤ 700 token、≤ 2 条、每条 ≤ 300 字符(每条下限 50 字符)。700 才真的放得下"两条满额中文块 + 抬头"(2×≈255 + ≈105);设成 500 时预算会先砍掉第二条 = 实际只注入 1 条 |
| 实测一次注入 | 128 / 167 / 395 / 458 token(视命中几条、正文多长) |
| 入库扩写 | 每块只送前 600 字符、每批 8 块、单次输出上限 240 token(格式不对整批丢弃,保留原词) |
| 查询改写 | 单次输出上限 120 token |
| 每日调用上限 | 默认 0 = 不限(想设就填,到上限停用到次日,面板会显示原因) |
「已删除」的那条闸门:早期版本有一个会话累计注入上限(默认窗口的 2%)。2026-10-07 按实测删掉了它——一天只有 19~21 次辅助调用,整场超长会话的插件总开销约 6 万 token,而主模型累积已用 5.6 亿 token(约 0.01%)。而累计闸门唯一稳定的效果是「命中率莫名其妙下降」:用户不会想到是自己多点了几次 ✕ 把额度花完了,只会觉得这插件后来不灵了。成本控制改由单次口径负责(上面那张表)。
量级表(用于判断"到底贵不贵"):
| 场景 | 辅助花费 |
|---|---|
| 5 次压缩 + 20 次 ✕(同一会话,估算) | ≈ 3.3 万 token |
| 同一场会话里主模型的累积用量 | 数亿 token |
| 占比 | ≈ 0.01% |
说明:3.3 万 token 来自
scripts/measure-savings.mjs在真实会话日志上重放生产代码路径的实测(该脚本会把每次压缩都过一遍入库管线并模拟扩写调用);"数亿"是主模型侧的累计口径,两者不是同一把尺子的同一段,只用于看数量级。 上表的"实测一次注入 / 一次总览"数字来自scripts/inspect.mjs对同一份真实记忆库(122 块:摘要 60 + 原文 62,共 116,747 字符)的试检索输出,可用同一条命令复现。
不想花这钱:面板 ⑦ 选「不调用大模型」即可,插件的核心功能(压缩入库、检索、注入)完全不受影响,只是"换个说法问"更容易漏。
三个层次(术语第一次出现时的解释):
$DSH_HOME/sessions/**/session.v4.jsonl.zstd,多帧 zstd 压缩包)。插件只读它,永不写入。为什么 L2 不能直接用 L0 顶替(这是刻意的冗余,不是重复劳动):
配套机制(都是为了让"注入"这件事本身不产生额外开销):
面板 ⑦「使用方式」三选一:
| 档位 | 含义 |
|---|---|
| 不调用大模型(默认) | 纯本地。提问零延迟、零 token;压缩后仍会注入总览与命中的参考。 |
| 调用主模型 | 用你当前会话的主模型补问法、换关键词;成本随主模型走。 |
| 调用指定模型 | 这两处改用你指定的模型(例如主对话用 pro、这里用更便宜的 flash)。 |
{"点":…, "关键词":[…], "时间意图":"both_ends"}),要么(改写被跳过时)是分页判读那一次回答里多出来的一个词 —— 两条路都不新增调用,调用次数一次都不多。优先级:改写给了就用改写给的(它是专门为这句话判的),否则用判读顺带给的;回执里的 timeIntent.via 写明来源(rewrite / pageread)。旧格式(纯数组 / 没有该字段 / 判读只回了编号)照旧兼容:一律当 none(= 不重排),不报错、也不重算。llmMode(off / main / custom)+ llmProvider + llmModel。选了「调用指定模型」后,入库与检索都用这一对;这一对为空就按「跟随主模型」处理(不会悄悄退回某个旧字段)。off 不清空你填过的提供方/型号——那是"上次选了什么"的记忆。dsh-super-memory.llm.jsonl——不含任何对话内容。本节逐条列出插件实际用到的主机能力与数据边界,全部可在仓库源码中核对。没有列在这里的能力,插件不会使用。
package.json 里只有 peerDependencies,没有 dependencies / devDependencies / optionalDependencies。peerDependencies 声明的 @deepseek-ai/* 都是 DSH 宿主自身提供的包,由宿主在运行时注入。ctx.inject 拿到的服务:必需 tools、systemPrompt、webServer;可选 llm(拿不到时模型辅助功能自动消失,其余照常工作)。$DSH_HOME/sessions/**(多帧 zstd 逐帧解码):绝不写入、绝不修改、绝不删除。$DSH_HOME/storages/session_projcache/sessions/<会话id>.json(用于在面板里显示与 DSH 侧栏一致的会话标题)。<工作区>\.dsh-compaction-memory\:<workspace>/.dsh-compaction-memory/
<sessionId>.jsonl 该会话的压缩记忆(一行一条块)
_trash/<时间>_<会话>/ 回收站(blocks.jsonl + manifest.json)
_audit.jsonl 删除 / 还原 / 清空的审计
_pairs.jsonl 未命中诊断的人工配对记录(不参与检索)
_readable/excerpts/*.md 「打开原文」生成的逐字摘抄(只在点 ✕ 时产生)
$DSH_HOME,可用环境变量 DSH_SUPER_MEMORY_HOME 指到任意目录)里只有这几个小文件:| 文件 | 内容 |
|---|---|
dsh-super-memory.settings.json | 设置(跨工作区生效,含面板登记的已知工作区列表) |
dsh-super-memory.diag.jsonl | 诊断日志(默认上限 4000 行,超了自动压缩) |
dsh-super-memory.llm-usage.json | 今日调用次数与 token 估算 |
dsh-super-memory.llm-cache.json | 查询改写缓存(30 天、最多 200 条) |
dsh-super-memory.llm.jsonl | 模型调用留痕(只有元数据) |
storeDir 可配置,但含 .. 的配置会被拒绝并回落默认值。.gitignore(仅在你自己点按钮时):面板 ④ 检测到「工作区是 git 仓库、而记忆目录还没被忽略」时会给出「帮我加忽略规则」按钮;点它,插件就往该工作区的 .gitignore 追加一行 storeDir/(文件不存在时顺带创建,并写一行说明注释)。不会自动改写:不点就一个字节都不写。追加动作会记进 _audit.jsonl。/session、/trash、/audit 等只读接口,只会把"当前会话的 cwd"登记进进程内存;设置文件里的「已知工作区」名单只在写操作(删除 / 还原 / 清空 / 改设置 / 加忽略规则)或会话内登记时才落盘。此前每次 GET 都会改写设置文件,而那份名单只有 40 槽 —— 用户的真实工作区被挤出去之后,面板与 ✕ 会一律报"未知工作区"。现在容量提到 200,并按"最近使用"淘汰。dsh-super-memory.diag.jsonl 的 recall 事件只记提问的短哈希与字符数(queryHash = sha256 前 8 位十六进制、queryChars),不含提问文本。自己机器上排障若确实需要明文,可设环境变量 DSM_DIAG_QUERY_PLAINTEXT=1(默认关;开了才会额外写 queryPlain)。queryHead(提问前 60 字符明文)。因为这个文件在 $DSH_HOME(跨工作区汇聚成一个文件),等于把用户问过的话另抄了一份到工作区之外,所以现已改为哈希。旧日志文件里已经写过的那几行明文不会自动消失,要清就自行删除该文件(删掉只影响排障历史,不影响记忆本体)。/diagnostics 回给面板),不落这个日志文件。locale/zh.json、locale/en.json 里的插件元数据是双语的,但设置面板本身的文案没有做 i18n,README.en.md 也说明了这一点)。assertInside() 校验——目标必须严格位于记忆目录之内(等于根目录也拒绝,..、绝对路径、符号链接逃逸一律拒绝)。_trash/ 可还原,并写审计日志。只有两种情况是永久删除:trashEnabled 显式设为 false 后的删除,以及面板的「清空回收站 / 彻底删除」——两者都写审计。lib/ 不引入 node:http(s) / net / dns / tls。fetch 全部指向本机 DSH 的插件接口 /api/dsh-super-memory/*,同源同进程,不产生对外流量。llm 服务调用你自己配置的模型。spawn(lib/routes.js 的 revealInFileManager()),唯一用途是「在系统文件管理器里定位文件」(面板的「浏览」按钮):Windows 用 explorer.exe /select,,macOS 用 open -R,其它用 xdg-open。spawn 不经过 shell,不接受任何来自请求的字符串作为可执行文件名或参数;目标路径由服务端自己用记忆目录根 + 会话 id 拼出,越界工作区直接 403。.env 读取、没有 keyring / 凭据存储访问。llm 服务,插件侧只填 provider + model 两个字符串名字;真正的鉴权与密钥由 DSH 掌握。locale/*.json 与"是否需要构建"locale/zh.json、locale/en.json 是手写源文件,不是构建产物;随包分发并被 exports 的 ./locale/*.json 导出。npm test 只是自检,不产出任何运行产物,也不被运行时依赖。本节写法说明:以上只写能从本仓库代码与
package.json核实的事实。若将来新增了本节未列出的能力,请同步更新本节。
环境:DSH 0.1.7-rc.2 / 0.2.0-rc.1 及以上(见 package.json 的 peerDependencies;已在 0.2.0-rc.2 客户端实测)。
安装(三种方式任选):
# ① 插件市场:搜 dsh-super-memory 直接装(若你的 DSH 版本带市场)
# ② 从本地目录装(<本机插件目录> 换成你 clone / 解压出来的路径)
plugin_manager action: install_bundle target: <本机插件目录>
# ③ 从 npm 装(已发布时)
plugin_manager action: install_bundle target: dsh-super-memory
装完重启 DSH 客户端:宿主插件代码在进程里缓存,不重启不生效。成功标志:设置里出现「超级记忆」这一节,工具列表里出现 history_read。
卸载:plugin_manager action: remove_bundle target: dsh-super-memory。
卸载不会自动删除工作区里的 .dsh-compaction-memory 与全局目录里的那几个文件,要手动清理(位置见第 8 节)。
面板怎么用(顶部三句话就够):
面板按"插件实际做的三件事"分板块(①②③),后面是 ④ 被保存的压缩内容、⑤ 回收站、⑥ 诊断与路径、⑦ 模型辅助。所有调参项收在顶部右侧的「参数设置」里(默认折叠):普通用户不展开也完全能用,想抠成本 / 命中率的人展开就能逐项调。所有参数改完即时生效,不需要重启。
常见问题:
<工作区>\.dsh-compaction-memory\)。如果那个工作区是 git 仓库,面板 ④ 会检测到并给一个「帮我加忽略规则」按钮——记忆里是对话原文,提交上去就收不回来了。摘抄(点 ✕ 的「打开原文」生成的逐字问答)如果早先被误提交过、已被 git 跟踪,删除后面板会提示你去执行 git rm --cached <摘抄目录>(.gitignore 对已跟踪文件无效,插件只删工作区里的文件,不会替你动 git 索引)。| 缺陷 | 后果 | 修法 |
|---|---|---|
| 路径穿越写入 | 会话 id 来自请求体,..\..\.. 之类会逃出记忆目录写到任意位置 | 文件名只留安全字符 + 落盘前断言目标仍在 root 之内(双重设防) |
| ✕ 的资料只在第一步可见 | 组装上下文每个 step 都跑一次,清空点写在那里 → 真正作答的那一步看不到它 | 清空点搬到 turn/end,且只在"跨轮"时清 |
| 跨会话串味注入 | 记忆库按工作区放,同工作区里躺着多个会话的块 → 模型说出你根本没说过的事 | /diagnose 的候选只取目标会话的记录,没有 session 字段的旧记录一律不匹配 |
回填 NaN 导致冷启动看不到块 | const added = backfill(...) 而内部是 += await … → added 恒为 NaN → 索引永不刷新 | 去掉错误的 await 链,写出真实计数并在落盘后失效索引 |
outChars 恒 0 | 思考型模型只吐 reasoning 不吐正文 → 留痕里输出量永远是 0,"没输出还是没记账"分不清 | 按分片累加真实长度,并把"思考"与"正文"分开记 |
| 毫秒级陈旧判定 | 用 Date.now() 判断"结果是不是上一轮的",同一毫秒内发布的新结果被误判成陈旧整块不显示 | 改用发布序号(自增),不做时间比较 |
| Hooks 顺序 / TDZ | ① 提前 return 之前漏掉 hook → 组件卸载,✕ 按钮凭空消失;② onClick 引用了定义在它之后的 const run → 点击抛 TDZ 且被静默吞掉,表现是"点了没反应" | 所有 hook 提到提前 return 之前;run 保持函数声明(提升),并加源码级断言钉死 |
npm test 覆盖不到宿主半边 | lib/host.js 一度被写成 UTF-16 乱码而测试全绿——因为三套脚本只 import 了少数几个模块 | 新增 scripts/host-smoke.mjs:每个文件非空 / 无 BOM / 无 NUL + 逐个 node --check + 宿主入口与关键模块真的能 import + 导出契约在位 |
apply(ctx) 从没被真调用过 | 注册项被注释掉、注册到错的键、apply 中途抛错 —— 整块功能静默消失而四套自检全绿 | host-smoke.mjs 造一个假 ctx 真的 await apply(fakeCtx),逐条断言"注册了 systemPrompt.context 提供者 / 订阅了 session/event / 注册了面板路由 / 注册了 history_read",并用"桩调用计数 > 0"证明真的执行到了;再触发一条真实形状的 compaction/summary |
routes.js 的请求链零执行 | HTTP 方法写错、路径字符串写错、写守卫被摘掉都测不出来 | 用合成 req/res 真打 handler:GET /overview 200、未知路径 404、POST 缺来源头 403、只支持 GET 的路径发 POST 落 catch-all 404、带参 /session、/search 200 |
| 面板桩丢弃依赖数组与 cleanup | 桩里 effect 每渲染都跑、cleanup 从不执行 → "effect 依赖写错"在测试里完全不可见;控件绑错键也看不见 | 桩 useEffect/useMemo/useCallback 改为逐项 Object.is 比较依赖(不变不重跑、变了先 cleanup)、useRef 跨渲染复用;新增"控件 ↔ 设置键"绑定断言(8 个控件、种入不同取值) |
load() 不做上下界校验 | 手改设置文件塞 maxRawCharsPerCompaction: 1e21(巨量内存)/ protectRecentDays: 1e15(永远删不掉)被照单全收 | 抽出 INTEGER_BOUNDS 一张表,normalizeSettings(读盘)与 validatePatch(面板提交)共用;单元测试断言 1e21/1e15 读回后被夹到合法上界,并逐字段核对"表 = 面板数字框的 max" |
| 诊断日志含提问明文 | recall 事件写 queryHead(提问前 60 字符明文),而日志在 $DSH_HOME(跨工作区汇聚)→ 提问被另抄一份到工作区之外 | 改为 queryHash(sha256 前 8 位)+ queryChars;host-smoke 断言最后一条 recall 带哈希、与提问哈希一致、且整个文件里没有任何 queryHead |
| GET 产生持久写副作用 | 每次 GET 都把会话 cwd 登记进 knownWorkspaces 并写盘,40 槽被挤爆后用户真实工作区一律 403 | GET 只做内存登记(resolveTarget(..., {persist:false}) 走 resolveReadTarget),持久化只在写操作时发生;容量 40→200 + 最近使用优先 |
| 工具结果入库越界(上限形同虚设) | toolRecords 是"先切后拼":text.slice(0, maxChars) 之后才拼首行与截断说明 → 落盘长度 = 上限 + 60~70。夹具库实测 22/210 条越界,最大 4068(上限 4000);上限 200 时落盘 284 | 收口到一个纯函数(clampToolText/toolRecordText):先净化 → 再按最终长度切;并在 rawRecords 的合并出口再收口一次(两条工具出口都覆盖) |
| 自指噪声(插件源码进 L2) | 读自己源码产生的工具结果正文就是插件代码,"什么词都有"→ 通用吸引子,让任何逐字写在自己源码里的探针提问必然命中 | 入库侧一条窄规则:工具参数里的目标路径落在本插件目录内就不入 L2(selfSourcePath),并在诊断里记 selfSourceSkipped;不排除用户项目里的代码文件 |
| 面板自检把注释当代码 | usedKeys 对整份 client.js 匹配 effective('键'),注释里写过的键也算"面板用到的键" → 统计虚高 1 个假阳性(llmAssistEnabled),且"注释与代码不一致"永远发现不了 | 扫描前先 stripComments(最小词法扫描:保住字符串/模板字面量),键、CSS 类、API 路径、板块编号四处都用剥注释后的源码 |
lib/host.js 写成过 UTF-16 + NUL 的乱码(npm test 还全绿)。改文件一律用精确替换工具,改完 node --check。useRef 必须像真 React 一样跨渲染复用同一个对象(每次新建会让"只在首次渲染记一个值"的组件每帧重置,于是测试里的陈旧判定和真机行为不一致);② 桩 fetch 要按 URL 分派(宿主就是多个接口),全都回同一个 payload 会让跨接口读取永远读到 undefined。openLlm 写成 6)会去改另一个状态,断言看着还在、其实形同虚设。现状:本插件的所有参数由插件自己存($DSH_HOME/dsh-super-memory.settings.json,见 config.js 的 SettingsStore),界面是自己注册的整页设置板块(client.js 用 ctx.slots.inject('settings.section', …))。官方 DSH 0.2 提供的是另一条路:插件导出 export const Config = Schema.object({...}),字段要进表单必须标 .volatile(),宿主读 config.<字段>.get(),变更走 ctx.on('loader/volatile-update', …),值落在当前 profile 的 cordis.patch.yml(分 profile、不分工作区)。下面是一次只读探针的结论,说明为什么不迁移。
ctx.llm.listProviders())不可能由官方表单生成。settings.section(那是标准扩展点,不是绕过)。.volatile() 就静默失效——字段能写进文件,却不进表单、也不通知;@deepseek-ai/* 时,app.asar 内的 0.2.0-rc.2 与 profiles\node_modules 里那份 0.1.7-rc.2 谁生效(见下面的警告)。SettingsStore,迁移要把它们全部改一遍,而收益只是省掉几百行存储代码——本插件零老用户,不值这个风险。已采纳的唯一一条:探针里唯一"便宜且明确更好"的建议已经落地——「恢复默认设置」改为逐键重置并保留模型档位(SettingsStore.reset() + RESET_KEEP_FIELDS),不再删掉整个设置文件。原来的实现是 unlinkSync(settingsPath),用户点一次就把 llmMode/llmProvider/llmModel 一起丢掉(实测踩过)。
⚠️ 给未来维护者的警告:
C:\Users\Administrator\.dsh\profiles\node_modules\@deepseek-ai\*下的 junction 指向的是 0.1.7-rc.2,而桌面实际运行的是 0.2.0-rc.2(在app.asar内)。去读那层 junction 会得到过期结论(例如"官方没有这个能力"其实是"旧版的官方没有")。要判定官方能力,请在app.asar里读对应版本,或直接解包app.asar后核对。
| 版本 | 关键变化 |
|---|---|
| 0.1.0 | 三件事落地:压缩时入库(L1 摘要 + L2 只留对话文字)、压缩后注入 ≤300 token 总览、提问时本地检索命中才注入。记忆存在会话所属工作区。 |
| 0.1.1 → 0.1.2 | 修「本轮第一个请求看不到召回」:改从 agent/inbox/spliced 事件抓提问(它比运行上下文求值早几毫秒);新增 stickyRecall(命中块挂住不放,省一次快照追加)。 |
| 0.1.3 → 0.1.6 | 面板按"三件事"重构成板块;④ 拆出「被保存的压缩内容」+「回收站」,会话标题与 DSH 侧栏一致;全局数据目录可用 DSH_SUPER_MEMORY_HOME 搬离系统盘。 |
| 0.2.0 | 审计驱动的安全与正确性修复:路径穿越(连 root 本身也拒绝)、写操作 CSRF 守卫、sessionLogBytes 契约、改设置丢工作区名单、?limit=abc 传播成 slice(0,-NaN)、history_read 回落日志加 64MB 闸、states 加 LRU。新增 scripts/unit.mjs(失败即退出码 1)。 |
| 0.2.1 | 成本口径正式改为「用户选档位 + 单次严格控制」;删除思考强度设置键与继承路径(那是 bug:又慢又贵);工作区自动识别;新增宿主冒烟守卫 host-smoke.mjs。 |
| 0.2.2 | 辅助成本砍掉一半:工具结果块不再送去扩写(它靠文件名/标题就能检索到,而体量是对话正文的 3.3 倍)、每批 5→8 块、扩写输出上限 300→240;本地已强命中时跳过查询改写(省一次调用,结果不变);点 ✕ 立刻显示「检索中」、✕ 超时 4s→8s;留痕与用量开始记录 token 估算;修掉陈旧判定的毫秒比较;删除会话累计上限(默认日上限改 0 = 不限)。 |
| 0.2.2 之后(未改版本号) | ① 注入行改为"答优先 / 结论句优先":以前那一行是"问题 + 截断的正文",现在先取回答/结论句,回答为空才退回问题开头(lib/recall.js 的 itemText)——用户看到的"想起了什么"从"又念了一遍我的问题"变成"它记得我当时得出的结论"。② spawn 失败不再掀翻宿主:可执行文件不存在走的是异步 error 事件,原来没有监听者 → 未捕获异常直接杀进程(面板点一次「浏览」就能把 DSH 打挂);现在唯一的子进程出口立刻挂 error、失败只写 reveal-spawn-error 诊断。③ 摘抄跟着删除语义走:/delete、/trash/purge 现在会一并删掉 _readable/excerpts/ 里该会话的逐字摘抄(以前"删干净"是假的,可读原文还躺在磁盘上)。④ 写路径串行化:记忆库的 append 与读-改-写用 withFileLock 串起来,交错不再丢块。⑤ /diagnose 的模型改写限流:同一会话 60 秒最多 6 次(RateLimiter),挡掉"本机任意进程反复 POST 就等于反复花钱"(web server 本身不鉴权)。⑥ outTokensEst:留痕与用量按真实输出分片累加记账(思考型模型只吐 reasoning 时不再恒为 0)。⑦ 本轮的接线层自检与隐私/一致性修复见下一条。 |
| 本轮自检补强 = 接线层自检三件套(0.2.2 之后,未改版本号) | 第三方只读审查点名三处结构性盲区,都补成了"能变红"的断言:① host-smoke.mjs 真的调用 apply(ctx)(以前只 import,从不执行 —— 注册项被注释掉、开关接错键、apply 中途抛错都能四套全绿),断言 systemPrompt.context 提供者 / session/event 订阅 / webServer 路由 / history_read 工具逐条在位,并真触发一条 compaction/summary;② 用合成 req/res 真打一遍 routes.js(请求链此前在 npm test 里零执行):GET 200 / 未知路径 404 / 写操作缺来源头 403 / 方法分发 / 带参路径 200;③ 桩 React 改为尊重依赖数组并执行 cleanup,另加"控件 ↔ 设置键"绑定断言(8 个控件含开关/数字框/文本框/下拉,种入与默认不同的值再逐项比对 —— 这就是抓"开关接到错的键"的守卫)。另外:load() 与面板 API 共用同一张上下界表(手改设置文件塞 maxRawCharsPerCompaction: 1e21 不再被照单全收)、诊断日志不再写提问明文(改哈希,见第 8 节)、GET 不再产生持久写副作用(已知工作区 40→200,按最近使用淘汰)、harness.mjs 临时目录用完即清(异常路径也清)、titles.js 读侧路径补 safeSessionId、删除摘抄被 git 跟踪时面板追加 git rm --cached 提示。 |
| 本轮(体积/自指/标定/面板自检) | ① 工具结果入库体积收口:toolRecords 的"先切后拼"会让落盘记录 = 上限 + 首行 + 截断说明(实测 4068/上限 4000、上限 200 时 284);现在把"上限"定义成落盘记录文本长度,收口在纯函数 clampToolText/toolRecordText,并在 rawRecords 合并出口再收口一次(两条工具出口都覆盖)。② 自指噪声不入 L2:读本插件自己目录内文件的工具结果不再入库(窄规则,只看工具参数里的目标路径是否落在插件目录内;不排除代码文件),跳过条数记进诊断 selfSourceSkipped。**③ strongHitScore 按新分数尺度重标 |