DSH 插件:把会话历史最旧的部分折叠成一条消息,背后是无损本地存档。不靠模型写摘要,全部可还原。
30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add mili1360/dsh-history-fold该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · tool
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 8 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意未发布到 npm registry,仅可从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-history-fold(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=18 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 22:43:25
安全扫描自动 · 静态分析源码v2.7.0🟢 扫描:未发现敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。
发起外部网络请求
查看扫描证据(共 1 处,展示前 1 条)
- 发起网络请求lib/semantic.js:67
req = http.request({
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 折叠最旧会话
- 无损本地存档
- 全文可还原
README
由 DeepSeek 最新模型翻译生成dsh-history-fold(中文说明)
Topics: dsh-plugin · deepseek-harness · history-fold · lossless-archive · context-compaction · non-summary · recoverable · extractive-index
英文主文档见 README.md。代码注释保持中文,本文件是完整的对照说明。
DSH 插件:把会话里最老的一批 surface 节点折成一条消息,消息里带着无损的本地归档 路径,降低每轮请求带的历史 token 数。
- 装上什么都不做 ✓ 没有 config.json 时它不读、不写、也不记日志
- 无损 ✓ 被折的每条消息在折叠提交之前就已全文落盘,替换内容明确告诉模型去哪儿找
- 不改 node_modules ✓ 用的是和官方压缩器同一套公开机制,DSH 升级不会把它弄坏
符号说明:✓ = 实测确认过 · ✗ = 陷阱,别这么做 · ⚠️ = 注意 · ⇒ = 所以
为什么做
同一台机器上实测:一个长会话 vs 一个新会话
| | 长会话 | 新会话 |
| 历史 token | 497,141 | 少得多 |
| 输出/输入 比 | 0.20% | 0.64% |
展开完整文档(README 剩余 1 节)
dsh-history-fold(中文说明)
Topics: dsh-plugin · deepseek-harness · history-fold · lossless-archive · context-compaction · non-summary · recoverable · extractive-index
英文主文档见 README.md。代码注释保持中文,本文件是完整的对照说明。
DSH 插件:把会话里最老的一批 surface 节点折成一条消息,消息里带着无损的本地归档 路径,降低每轮请求带的历史 token 数。
- 装上什么都不做 ✓ 没有 config.json 时它不读、不写、也不记日志
- 无损 ✓ 被折的每条消息在折叠提交之前就已全文落盘,替换内容明确告诉模型去哪儿找
- 不改 node_modules ✓ 用的是和官方压缩器同一套公开机制,DSH 升级不会把它弄坏
符号说明:✓ = 实测确认过 · ✗ = 陷阱,别这么做 · ⚠️ = 注意 · ⇒ = 所以
为什么做
同一台机器上实测:一个长会话 vs 一个新会话
| | 长会话 | 新会话 |
| 历史 token | 497,141 | 少得多 |
| 输出/输入 比 | 0.20% | 0.64% |
| decode 速度 | 59 tok/s | 176 tok/s |
| 首 token 延迟 | 约 2.5 秒 | 约 2.5 秒 |
结论:上下文长度拖慢的是「解码」,不是「首 token」 ✗ 所以「上下文不影响速度」这个说法只对前半句成立。
怎么做的
和官方 dsh-compaction-basic 完全相同的机制,一个字节的 node_modules 都不改:
session.append('user/message', message, {
surfaceOp: { op: 'replace', start, end },
sourceEventSeqs: [...每一个被折的 seq...],
})
dsh-session 随后把替换节点 splice 回被折区间的原位置,时间顺序不变。
触发线不是固定的 25 万 token:默认是"上下文窗口的 0.75",折到窗口的 0.18 ✓ 窗口能解析出来就按比例走,config.json 里显式写了绝对值则绝对值优先 ✓ 详见 配置 里的解析顺序 ✓
写之前核对过的硬规则(全部来自真实源码,不是猜的)
| 规则 | 出处 |
| surface.nodes 是普通 seq 数字数组,不是对象 | dsh-session |
| surfaceOp 必须恰好 3 个键 op/start/end | dsh-session |
| sourceEventSeqs 必须包含每一个被折节点、非空、每项严格小于新事件 seq | dsh-session |
| user/message 的 data 就是消息本体,需要 id/role/source.kind/content | dsh-session |
| 切点必须「工具调用配对平衡」 | 见下 |
「平衡」是按 @deepseek-ai/dsh-compaction 的 tool-pairing 规则自己重写的:沿 surface 顺序走一遍,assistant/message 里的 tool-call 块加 N、tool/result 减 1,计数为 0 的位置才是安全切点 ✓ 既不依赖 DSH 安装路径,也不会把工具调用和结果切开 ✓
替换内容 = 索引,不是摘要
进入上下文的那条消息是通知 + 抽取式索引 ✗ —— 每一行都是那条消息自己的原文前缀 ✓ 不调模型、不改写事实 ✓
[dsh-history-fold] 614 earlier messages (surface seqs 223995..387899) were folded out of the
visible history so this session stays fast.
NOTHING WAS DELETED. Their full text is archived at:
<$DSH_HOME>/history-fold/archive/session-….jsonl To read any of it verbatim, search that file with the pwsh or grep tools ... The one-line index below is extractive, not a rewritten summary ... (sampled: every 3th of 614 folded messages) [0] user/message (plugin) seq=223995: This is an automatically generated checkpoint …
索引超预算时抽样(每 n 条取一条)而不是截断 ✓ 并如实写明抽样比例 ✓
分层索引:路径里有路径(2026-10-15)
上下文里那份索引超预算就抽样,而抽样步长封顶在 64 ⇒ 折得越多,越老的那段索引越粗,最后退化成「seqs A..B 里大概有东西」✗
所以每次折叠都多做一件事:把没抽样的完整索引写到磁盘,上下文里只留它的路径 ✓
上下文里那条折叠消息
|- 路径 -> $DSH_HOME/history-fold/index/<会话id>/f100-4500.md <- 该段的完整索引(不抽样)
| \-- 路径 -> f20-99.md <- 更早那次折叠的完整索引
| \-- 路径 -> f0-19.md
\-- 路径 -> $DSH_HOME/history-fold/archive/<会话id>.jsonl <- 全部原文
两条关键规则:
1. 折叠消息里带一行机器可读的标记 ✓ [fold-index] seqs=A..B messages=N file=<层索引路径> ⇒ 下次它自己被折掉时能被解析出来,而不是被截成 160 字符的桩 ✓
2. 这一行永不被抽样掉 ✓(抽样只抽普通消息行) ⇒ 否则它盖住的那段更老的记录就成了孤儿 —— 上下文里再也没有路能走到它 ✗
于是每一层索引都靠自己那份磁盘文件活着 ✓ 上下文里只留最短的这一层 ✓ 打开一份层索引,能看到它盖住的更老的层索引路径(## older tier files)⇒ 一层层往下走,最终到原文归档 ✓
读它不花上下文 ✓ —— 不读就等于不存在,读了才付费 ✓
⚠️ 与官方压缩的区别仍在这里:官方每次都重新写一份摘要(本机实测一个会话压了 17 次,每次输入都带着上一份摘要,输出新的把它盖掉)⇒ 老信息被反复重写 ✗ 而这里是写盘一次 + 留路径,落盘的东西此后不再被动过 ✓
安全网
| 机制 | 作用 |
| 折之前先归档 | 被折掉的每一条消息的全文写进 $DSH_HOME/history-fold/archive/<会话id>.jsonl ✓ |
| 归档失败就不折 | 写归档抛异常 ⇒ 直接放弃这次折叠 ✓ 宁可慢也不丢 ✓ |
| 只留抽取式索引 | 替换内容不是模型写的摘要 ✓ 没有 LLM 参与 ✓ |
| 层索引写失败不挡折叠 | 归档里已有全文 ⇒ 索引写失败只记一行 index-failed,照常折 ✓ 消息改指向原文归档 ✓ |
| 只动最老的一段 | 最近的 keepRecentNodes 个节点永不触碰 ✓ |
| 切点必须配对平衡 | 有未闭合的工具调用时绝不切 ✓ |
| 只在一轮开头折 | 步骤中间绝不折 ✓ |
| 出错一律放行 | 任何异常都 next() ✓ 绝不断你正常对话 ✓ |
| 默认就是开着 | 没有 config.json ⇒ enabled:true + dryRun:false ⇒ 越过触发线就真折 ✓ 想只看不动写 {"dryRun":true} ✓ |
| 第二道闸 | 上一轮没折成、这轮还停在 emergencyTriggerRatio(0.8)之上 ⇒ 放宽参数再折一次 ✓ 日志里带 emergency: true ✓ |
⚠️ 2026-10-17 改:默认值从「装上什么都不做」改成「装完就干活」 ✓ 原来是 enabled:false + dryRun:true,README 里还管那叫“安全默认” ——那不是安全,是缺席 ✗ 一个专门用来压体积的插件,装完却一趟都不干,等于没装 ✓
现在的兜底是写到位而不是关掉:折前逐字归档、归档失败就放弃、{"enabled":false} 随时叫停 ✓
折不动怎么办:第二道闸(2026-10-17 加)
正常情况折不动是不成立的 —— 触发线一过,就把能折的一段一次折到底(折到 lastFoldableIdx,目标 180k),所以在同一轮里它不可能“折一点然后放弃”。
真正会让它空转的只有三个很窄的口子:
1. 最近还保的 keepRecentNodes 个节点,它们自己就超过触发线;
2. 存档写失败(按设计直接放弃,宁可不折也不丢);
3. 找不到配对平衡的切点(整个可折区的工具调用/结果都配不成对)。
为了不让这三个口子把 surface 一路顶到窗口上限,加了第二道闸:
- 下一轮如果还停在窗口的 0.8(emergencyTriggerRatio,1M 窗口 = 80 万)之上 ⇒ 说明上一次没折成 ⇒ 放宽参数再折一次:
- 最近节点从 keepRecentNodes(60)降到 emergencyKeepRecentNodes(12)
- 折叠下限从 minFoldNodes/minFoldTokens(20 / 20000)降到 emergencyMinFoldNodes/MinFoldTokens(4 / 2000)
- 机制完全不变:照样逐字归档、照样不切工具对、照样只在一轮开头折 ✓
- 只是“客气程度”降低:允许少留一点最近历史 ✓
- 日志里这次折叠带 emergency: true,事后能分清是哪条路 ✓
第二道闸永远不会低于第一道:emergency 取 max(绝对值, 触发线),否则它会在同一轮里和正常折叠重复触发 ✓
配置
文件:$DSH_HOME/dsh-history-fold/config.json(默认 ~/.dsh/…;热读,改完不用重启)
{
"enabled": true,
"dryRun": false,
"profile": true,
"triggerSurfaceTokens": 250000,
"targetSurfaceTokens": 180000,
"triggerRatio": 0.75,
"targetRatio": 0.18,
"keepRecentNodes": 60,
"minFoldNodes": 20,
"minFoldTokens": 20000,
"archive": true,
"archiveDir": "history-fold/archive",
"digestMaxChars": 30000,
"digestLineChars": 160,
"indexTree": true,
"indexDir": "history-fold/index",
"indexFull": true,
"onlyAtTurnStart": true
}
装完就是开着的:默认会真折 ✓ 但只在窗口占用过 triggerRatio(0.75)时才动手 ✓ 那是内置压缩(0.8)下面的那条线 ✓ 而且每个被折的节点都先逐字存档再离开可见历史 ✓ 想先只看不写,就建配置写 { "dryRun": true },看日志里的数字满意了再改回 false ✓
| 键 | 默认 | 说明 |
| enabled | true | 总开关 ✓ false = 加载了但完全空转 |
| dryRun | false | true = 只看只记,绝不写 |
| profile | true | 每轮写一份体积剖析 |
| triggerSurfaceTokens | 250000 | 绝对触发线,兜底用(见下面的解析顺序)✓ 写在 config.json 里就优先于 triggerRatio |
| targetSurfaceTokens | 180000 | 绝对目标,兜底用 ✓ 同上 ✓ 必须够得着(见下) |
| triggerRatio | 0.75 | 可见历史超过上下文窗口的这个比例才折 ✓ 窗口能解析出来、且没写绝对值时用它 |
| targetRatio | 0.18 | 折到大约窗口的这个比例就停 |
| emergencyTriggerRatio | 0.8 | 第二道闸。下一轮还停在这个比例之上 ⇒ 上一次没折成,用下面的放宽参数再折一次 |
| emergencyTriggerSurfaceTokens | 800000 | 第二道闸的绝对值形式(窗口解析不出来时用)✓ 永远不会低于正常触发线 |
| emergencyKeepRecentNodes | 12 | 急折时还允许保住多少最近节点(与 keepRecentNodes 取小) |
| emergencyMinFoldNodes | 4 | 急折的节点数下限 |
| emergencyMinFoldTokens | 2000 | 急折的 token 下限 |
| keepRecentNodes | 60 | 最近的这么多节点永不折 |
| minFoldNodes | 20 | 一次折得太少就不值当 |
| minFoldTokens | 20000 | 同上,按 token 算 |
| archive | true | 写无损归档 ✓ 别关 |
| archiveDir | history-fold/archive | 相对 $DSH_HOME |
| digestMaxChars | 30000 | 上下文里那份索引的上限 ✓ 超了抽样不截断 |
| digestLineChars | 160 | 索引里每条显示多少字符 |
| indexTree | true | 写层索引、并把路径放进上下文 ✓ 关掉就退回"只有抽样索引"的旧行为 |
| indexDir | history-fold/index | 层索引目录,相对 $DSH_HOME |
| indexFull | true | 写不抽样的完整索引到磁盘 ✓ 关掉则只收集指针、不落新文件 |
| onlyAtTurnStart | true | 只在一轮开头折 |
| logFile | logs/history-fold.jsonl | 相对 $DSH_HOME |
| memoryFeed | false | 折完把同一份摘要 POST 到记忆库 |
| memoryFeedUrl | — | 例:http://127.0.0.1:7999/memory-eternal/api |
⚠️ 目标必须够得着:至少有 keepRecentNodes 个节点会留下,换算成 token 就是一条下限 ✓ 目标低于这条下限永远达不到 ⇒ 每轮都记 no-safe-cut ✓ (实测踩过:keepRecentNodes: 9 + targetSurfaceTokens: 2000 就是这种自相矛盾)目标大于等于触发线同样永远够不着(折到目标就停,而 surface 一开始就高于触发线)⇒ 走的是同一条 no-safe-cut,没有为比例另造行为 ✓
两个阈值是怎么算出来的
triggerSurfaceTokens / triggerRatio(目标同理)每轮按这个顺序、按 key 各自解析:
1. config.json 里写了这个 key ⇒ 用这个绝对值 ✓ 写了 triggerSurfaceTokens: 300000 就是 300000,窗口解析得出来也不改变它 ✓ 注意"写了"指的是原始文件里存在这个 key,不是合并后的配置对象 —— DEFAULTS 里也带着这两个 key,用合并对象判断的话永远命中,比例就永远轮不上 ✓
2. 否则,窗口能解析出来 ⇒ floor(窗口 × 比例) ✓ 窗口取自
ctx.llm.resolveModelInfo(provider, model, signal).context.contextWindow,
和官方 dsh-compaction-basic 算自己那条线用的是同一个查询 ✓
3. 否则 ⇒ 用出厂的绝对默认值,日志里写 default ✓
为什么默认是 0.75 / 0.18,而且必须压在官方那条线下面。 官方 dsh-compaction-basic 在 thresholdRatio × contextWindow 触发 —— 默认 0.8 × 100 万 = 80 万 —— 并保留 0.16 × 窗口(16 万)✓ 它的替代品是模型写的摘要,是有损的 ✓ 0.75(100 万窗口的 75 万)刚好压在那条线之下,这就是"让摘要器永远不触发"的办法; 0.18(18 万)对应它的 0.16 ✓ 这两个默认值就是本项目自己那套绝对值(75 万 / 18 万,即 100 万窗口的 0.75 / 0.18)写成了比例,换个窗口也照样成立 ✓
每一条 profile / 折叠日志都带着这些字段:windowTokens(解析到的窗口,没有就是 null)、 triggerSource / targetSource = absolute | ratio | default ✓ 比例不在 (0, 1) 内(0、1、1.5、"0.75"、null)不会抛错、也不会变成一条坏阈值:按第 3 条兜底,并在同一行写 triggerRatioInvalid / targetRatioInvalid 说明原因 ✓
和官方压缩、以及和"叫 fold 的其他插件"的关系
| | 官方 dsh-compaction-basic | 本插件 | UI 折叠类插件 |
| 历史被换成什么 | 模型写的摘要 | 抽取式索引 + 归档路径 | 什么都不换,请求原样不动 |
| 每次折叠的成本 | 多一次模型调用 | 零,压根不调模型 | 零 |
| 原文还在吗 | ✗ 只剩摘要 | ✓ 逐字写在 $DSH_HOME/history-fold/archive/ | n/a |
| 能找回来吗 | ✗ | ✓ 一条 rg 就行 | n/a |
| 改的是什么 | surface(发给模型的东西) | surface(发给模型的东西) | 显示(长消息在界面上折起来看) |
| 触发线 | thresholdRatio × contextWindow,默认 80 万 | triggerRatio × contextWindow(0.75 ⇒ 75 万),压在官方那条线下;写了绝对 triggerSurfaceTokens 仍然优先 | n/a |
名字里带 fold 的插件,很多是界面折叠:把一条长消息在 UI 里折起来方便看, 请求体一个字节都不动 ✓ 本插件恰好相反 —— 完全不碰界面,它搬的每一个字节都是为了下一轮请求的 token ✓ 装之前先想清楚你要的是哪一种。
同一个问题的磁盘侧(会话日志因为"定稿后的流式碎片永不清理"而膨胀)见 dsh-session-vacuum ✓
判定用的是真实 token 计量器(ctx.tokenMeter.measure)✓ 看不到计量器就拒绝折叠 ✓ 因为节点体积差好几个数量级,用节点数当预算是不成立的 ✓
日志
$DSH_HOME/logs/history-fold.jsonl,每行一个 JSON:
- profile —— 每轮一份体积剖析:surfaceTokens、按类型/来源的 token 分布、最大节点、以及各 keepRecent 能省多少 ✓ 策略就是照它调的 ✓
- idle —— 没到触发线
- no-safe-cut —— 到了线但找不到安全切点,或折得太少
- no-meter —— 看不到计量器,拒绝盲折
- would-fold —— dry-run 下「本来会折多少、从哪到哪、省百分之几」
- folding / folded —— 真折了(两行都带 surfaceTokens / foldTokens / startSeq / endSeq)
- archive-failed —— 归档写不进去 ⇒ 这次不折
- error —— 出错了(一律放行)
以上每一行都带着本轮实际用到的阈值,不用猜走的是哪条路:
- trigger / target —— 本轮真正用的两条线(token)
- windowTokens —— 解析到的上下文窗口;解析不出来就是 null
- triggerSource / targetSource —— absolute(config.json 里写了)/ ratio(floor(窗口 × 比例))/ default(出厂绝对兜底)
- triggerRatioInvalid / targetRatioInvalid —— 只在比例不可用、按第 3 条兜底时出现且为 true
- via —— 哪条路拿到计量器、哪条路拿到 llm(例:ctx.tokenMeter|llm:ctx.reflect.get/agent.options)
折完之后怎么找回原文
替换内容里带着归档文件路径 ✓ 直接用普通工具搜,不需要任何新工具 ✓ 归档是每行一个 JSON:{seq, type, src, text} ✓
rg -n "要找的词" "$env:USERPROFILE\.dsh\history-fold\archive\<会话id>.jsonl"
更老的东西怎么找(2026-10-15 起):上下文里那条折叠消息还会给出层索引路径 ✓
1. 先读它给的那份层索引(...\history-fold\index\<会话id>\f<起始seq>-<结束seq>.md)
2. 那份文件里 ## older tier files 一节列着它盖住的更早的层索引路径 ⇒ 一层层往下走
3. 走到 ## message index 里想找的那条,拿到 seq,再去归档里搜那条原文 ✓
每一层都是普通 markdown ✓ 用 read / rg 就行 ✓ 不读不花上下文 ✓
测试
npm test # 四套都跑
npm run test:fold
npm run test:profile
npm run test:edge
npm run test:ratio # 窗口比例:解析顺序、兜底、非法比例
npm run test:mutate # 故意塞回 10 个真实缺陷,要求每套都变红
全离线 ✓ 不碰运行中的 DSH ✓ 不碰真实 DSH_HOME ✓(测试里把 DSH_HOME 指到临时目录) ratio-tests 用的 llm 是三行假对象 ✓ 不 import 任何真实 DSH 服务 ✓
现行规模:fold-tests 15 项、profile-tests 8 项、edge-tests 19 项、 ratio-tests 31 项;变异测试 10 / 10 全部被抓 ✓
ratio-tests 覆盖:比例路径(floor(窗口 × 比例),含非整数乘积向下取整)、绝对覆盖(写了 triggerSurfaceTokens / targetSurfaceTokens 时它赢,另一个 key 仍按比例)、窗口解析不出来的每种情形(没有 ctx.llm、resolveModelInfo 抛错、没有 context、窗口为 0 / 负数 / 小数 / 字符串、agent 上没有 provider+model)、非法比例(0、1、1.5、 "0.75"、null、-0.5、NaN)、以及目标够不着时走既有的 no-safe-cut,外加三条取 llm 的路径(ctx.llm / ctx.reflect.get / payload.agent.ctx.llm)✓
覆盖:无 config.json 必须 inert、关闭时不写不记、未达阈值不折、dry-run 不写、 surfaceOp/sourceEventSeqs/切点平衡逐项校验、归档必须先于折叠且正文逐字保留、不在步骤中途折、surface 与日志不一致时拒绝折、有未闭合工具调用时不折、永远调 next()、异常时放行并记日志 ✓
⚠️ 给贡献者:test() 必须 await fn() ✗ 同步的框架会在任何 async 断言执行之前就打 ok,并且让脚本先走到 process.exit(0) ⇒ 整套测试静默变成空跑 ✓ 本项目曾经带着这个 bug 跑了很久(对外一直报「13 passed, 0 failed」,修好后立刻变成「6 passed, 7 failed」)✓ 详见 CHANGELOG.md
装 / 卸
挂载点在 ~/.dsh/profiles/<profile>/cordis.patch.yml:
- insert:
- id: history-fold
name: 'file:///path/to/dsh-history-fold/lib/index.js'
- 需要重启 DSH 才加载
- 想临时关掉:把 config.json 里 enabled 改回 false(热读,不用重启)
- 想彻底卸掉:删掉上面那两行,重启 ✓ 归档目录是它唯一留下的东西
要求 / 兼容性
装上之后要确认它真的在工作 ✓ 光"profile 起得来"不算数 ✗ 插件可能加载得好好的、路由也活着,却把宿主弄坏 —— 我们真踩过 ✓ 验收动作: 新建一个会话、发一条消息 ✓ 然后看 $DSH_HOME/logs/history-fold.jsonl 里有没有 profile 行(观察模式),或 idle / would-fold / folded 行 ✓ 如果连新会话都建不出来 ⇒ 把上面那行挂载删掉、重启 ✓
针对 DSH 0.1.0-rc.7 开发并测试 —— 这是一个魔改版,不是官方原版。别的版本可能把服务或事件名改掉:本插件用到 ctx.on、ctx.effect、ctx.tokenMeter、ctx.reflect、ctx.llm 和 agent/pre-step 事件。
DSH 0.2.0(实测 0.2.0-rc.2)—— 已支持
0.2.0 上完整验证过:连折三次、每一轮都正常结束、折叠消息写进了盘上的 session.v4.jsonl.zstd、重启后会话重载成功并继续折、磁盘上的层索引里也有套娃指针 ✓
0.2.0 改了四处 API,其中三处是静默失败:
| 改了什么 | 0.1.x | 0.2.0 | 写错的后果 |
| 读事件 | session.events,每条自带 seq | 没有这个成员;日志是位置数组(eventAt(seq) 就是 log[seq]),另有 snapshotEvents() | 每个节点读成 unknown / 0 字节 ⇒ 永远找不到安全切点 ⇒ 永远 no-safe-cut,而且不报任何错 |
| 折叠范围的键 | {op:'replace', start, end} | {op:'replace', startSeq, endSeq} | isReplaceOp 直接抛错,折叠被拒 |
| 表面头部 | 表面里没有系统消息 | node 0 是系统提示,受保护 | 任何碰到 node 0 的 replace 直接抛错:"may be rewritten only by a system/message over exactly that node" |
| 消息的 source | {kind:'plugin', plugin:X} | 这个包装已退役;消息必须带生产者自己的类型名 —— 第三方插件是 plugin:<名字> | append 成功,然后写盘时的行准入抛错 ⇒ 那一轮永远不结束、会话不再落盘、之后每一轮都卡死。宿主一个字都不报 |
插件会按宿主自动选 replace 的键形状和 source 拼写,并且从受保护头部之后开始折;头部的 token 会留在"够不够得着目标"的算术里。日志行会写明用了哪种形状(surfaceOpKeys)和跳过了几个前缀节点(protectedCount)。
版本对不上时,插件不会让 profile 起不来 ✓ 它会直接停用,并在 logs/history-fold.jsonl 里留一行:
{"mark":"deactivated","reason":"registration-failed","step":"services","error":"..."}
万一装上之后 profile 根本起不来:把 <profile>/cordis.patch.yml 里挂载它的那几行删掉(若你是用 dsh.profile.bundles 装的,就把数组里的 "dsh-history-fold" 去掉),然后重启:
- insert:
- id: dsh-history-fold
name: 'file:///path/to/dsh-history-fold/lib/index.js'
已知边界
- 默认阈值是按 100 万上下文窗口调的 ✓ 机制本身与适配器无关,但出厂绝对兜底值不是通用的 ✗ —— 这正是加比例的原因:窗口能解析出来时,两条线就跟着窗口走 ✓
- ctx.llm 这条取值路已经在真实 DSH 上跑通了 ✓(2026-10-12 实测,沙箱实例、web profile)配置里只留比例、不放绝对键时,日志记的是 windowTokens:1000000 / trigger:750000 / target:180000 / triggerSource:"ratio" / via:"ctx.reflect.get|llm:ctx.reflect.get/agent.options" —— 也就是说它走的是 ctx.reflect.get 拿到 llm、再从 agent.options 取 provider/model ✓ 取值方式与 resolveMeter() 一致(先直接属性,再 ctx.reflect.get,再 agent.ctx),全程不抛错 ✓ 万一你的 build 三条路都不通,日志里会是 triggerSource: "default",行为与加这个功能之前完全一致 ✓
- 折叠确实减少了模型能看到的东西 ✓ 归档保证可找回,但不是没代价:读回一次要花一次 rg 往返 ✓
- 目前不与 dsh-compaction-basic 协同,只是压在它的触发线之下 ✓
相关插件
同一套东西,三个插件各治一头 —— 都是"会话越跑越长、越贵越慢"这件事:
- dsh-session-vacuum —— 开机后把会话日志里 "定稿后没人读"的流式碎片删掉,长会话加载时不再膨胀成几个 GB ✓ (和本插件不在同一层:那个减的是磁盘上的日志,本插件减的是每轮请求带的 token)
- dsh-proactive —— 长任务在 agent 回合结束后才跑完时,把它叫醒主动汇报 ✓
许可
MIT,见 LICENSE。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
mili1360/dsh-history-fold
DSH 插件:把会话历史最旧的部分折叠成一条消息,背后是无损本地存档。不靠模型写摘要,全部可还原。
相关插件 · 工具 / 效率
破甲管控台kingcharleslzy-ai/dsh-purge
dsh-purge Community · DeepSeek Harness 社区维护版:宿主兼容、状态诊断、提示词管理、更新与本机备份回滚。基于小杨 / YuJunZhiXue 的 dsh-purge。
★ 4,330已汉化DeepSeek 客户端
74.5
网文长卷lingfengQAQ/webnovel-writer
基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200…
★ 7,636已汉化DSH
72.2
多智能体协作HKUDS/AutoSwarm
AutoSwarm: Unify Your Agents. Unify Your Context (Codex · Claude Code · DeepSeek Harness · Muse · Grok · WorkBuddy· nanobot · and more)
★ 2,349已汉化DSH
71.6
Vox 拼贴视频生成Alisa0808/vox-director
🎬 Vox Director拼贴动效导演
★ 2,129已汉化DSH
71.4
Agent 演化引擎Q00/ouroboros
智能体 OS:智能体自己变聪明,我们只守住底线:面试准入、分阶段评估、带预算的进化循环。MCP 服务器,14 种运行时:Claude Code、Codex CLI、Gemini CLI、OpenCode、Copilot、Kiro 等。
★ 6,311已汉化DSH
71.4
智能体运行框架路由HarnessRouter/harnessrouter
智能体 harness 的统一接口。
★ 3,984已汉化DSH
71.3
这是你的仓库?认领条目