dsh-smart-compact
hoyyang/dsh-smart-compact
DeepSeek Harness용 Codex 스타일 컨텍스트 자동 관리입니다. 요약 모델 호출 없는 무손실 롤오버, 작업별 인계 앵커, 압축 후에도 남는 작업 메모, 보관된 창의 원문 회상을 제공합니다. 오래된 메모는 최신 정보처럼 보증하지 않고 명시적 경고로 낮춥니다. 원클릭 작성 버튼과 /smart-compact 명령, 3단계 자동 롤오버, 필수 앵커 정제를 제공하며 설정 없이 작동합니다.
설치
dsh plugin --profile web add github:hoyyang/dsh-smart-compactREADME
dsh-smart-compact
Codex-style context window auto-management for DeepSeek Harness. / DeepSeek Harness 的 Codex 式上下文窗口自动管理插件。
给模型装上"自己管理上下文窗口"的五件套:看得见剩余预算、快满时被提醒交接、写跨压缩存活的工作笔记、按需回查被归档的原始内容、以及一个零模型调用的换窗压缩引擎(替代传统的"LLM 摘要压缩")。
Give the model Codex CLI's new context strategy: live budget visibility, wrap-up reminders, working notes that survive compaction, on-demand recall of archived original content, and a rollover compaction engine that switches to a fresh window with zero summarization model calls.
Install / Update 安装与更新
dsh plugin --profile web add dsh-smart-compact
dsh plugin --profile web update dsh-smart-compact@latest
开箱即用(零配置):所有配置键均为可选项,默认值即上线行为——装完启动 dsh web 即生效,无需任何设置步骤。No build step on the user side. 安装后启动 dsh web 即可。
有啥用 Features
- 零模型调用换窗:窗口快满时机械切换到新窗口,全程不调用一次摘要模型,省钱且零幻觉。
- 100% 无损归档:旧窗口内容原封不动进入本地持久流,不是"压缩摘要",随时可逐字回查。
- 交接锚点:新窗口第一条消息自带任务定向(Task / Last action 两行机械推导),不用猜"我刚才在干嘛"。
- 笔记新鲜度门(v0.5.2):交接笔记落后实际进度时,锚点自动降级为 ⚠️ STALE 警示并附机械活动尾,绝不背书过期状态。
- 工作笔记 ctx_notes:write / append / read / search / list 五操作,跨压缩、跨换窗存活。
- 无损回查 ctx_history:list 窗口清单、read 按 seq 精确回读、search 全文检索归档内容。
- 预算可见 get_context_remaining:当前会话剩余 token 预算(total / window / remaining)实时可查。
- 主动换窗 new_context:模型自己请求开新窗口,本步采样完成后生效,不打断当前步。
- 人工命令 /smart-compact:一条命令立即换窗,回执为人读中文,成功 ✅ / 失败 ❌ 前缀,绝不静默降级。
- 一键换窗按钮:输入框工具行 26px 正圆按钮,点击等同执行 /smart-compact,草稿自动保护。
- 三带自动换窗:quiet → grace 提醒 → forced 自动执行,宿主事件驱动,阈值可配。
- 强制脱敏:引擎自动生成的锚点文本过手机号 / 邮箱 / 证件号 / 企业名脱敏,命中只记数量不回显原文。
- 提醒触达可观测:25/50/75% + grace 提醒的触达计数写入 engine-state.json,提醒静默失效从此可查。
- 运行探针:engine-state.json 随时可查当前模式、窗口号、换窗计数与最近失败原因。
30 秒上手 Quick start
- 安装:
dsh plugin --profile web add dsh-smart-compact。 - 启动
dsh web,引擎自动武装(auto 默认开启,无需任何配置)。 - 正常开一个会话干活,输入框右侧的上下文消耗表实时可见。
- 消耗到 25 / 50 / 75% 时模型会收到分级提醒,提示它写交接笔记。
- 到 85% 缓冲带,模型收到 grace 提醒:该写 ctx_notes 了。
- 任意时刻都可以点输入框旁的金色圆按钮(或敲 /smart-compact)立即换窗。
- 换窗后新窗口第一条消息就是交接锚点,注明窗口号与归档区间。
- 新窗口用 ctx_notes read 继续笔记,用 ctx_history search 回查旧细节。
- 随时
cat ~/.dsh/dsh-smart-compact/engine-state.json查运行模式与换窗计数。 - 不想要了:
dsh plugin --profile web remove dsh-smart-compact,卸载即净。
使用场景 Use cases
- 长会话自动续命:一整天的编码会话快满时自动换窗,任务不中断、上下文不丢。
- 手动早换窗:感觉话题该收束了,随手一键换窗开新局,不用等阈值。
- 跨会话接力:昨天的会话今天继续,新窗口从锚点 + 笔记精确接上,不用翻聊天记录。
- 事故复盘考古:"上周那个报错到底怎么定位的"——ctx_history search 按 seq 逐字回读原始事件。
- 多窗口任务定向:换完窗不怕"任务盲续接",锚点 Task 行直接写明当前任务。
- 笔记滞后防护:手动换窗太急没来得及写笔记?锚点 STALE 警示明说,新窗口先核对再干活。
- 敏感信息防护:锚点自动脱敏手机号 / 邮箱 / 证件号 / 企业名,自动传播面不漏个人信息。
- 成本控制:零摘要模型调用,换窗零 token 成本;预算提醒帮模型主动规划交接。
- 多引擎共存:单槽谦让机制,与 dsh-glm / dsh-kiro 等其它压缩引擎和平共处。
- 运维可观测:engine-state 探针记录每次换窗的触发源、拒绝原因与预算测量状态。
演示效果 Demo output
以下全部为真机实测输出(本机 2026-09-29 真实会话与干净 staging 回装):
- 换窗后新窗口收到的交接锚点开头:
[smart-compact rollover] Context window #2 opened. The previous window was archived in full — nothing was summarized away or lost. - 手动换窗回执(GUI 卡片首行):
✅ 已换窗 → 窗口 #2 · 归档 window #1(223 events / ~121k tokens) ctx_history list输出:window #1: seqs 10–827 (223 events, ~121034 tokens archived) · live window: #2- 笔记滞后时锚点自动降级:
⚠️ STALE — 最后笔记 06:18Z 之后仍有 429 条活动;先 ctx_history 核对,勿直接信任笔记 - 脱敏打点日志:
handoff anchors sanitized — 3 redaction(s)(只记数量与类别,绝不回显原文) - 引擎探针实时状态见下方命令输出。
$ dsh --profile web --dump-config | grep -A2 smart-compact
# == dsh-smart-compact
- id: dsh-smart-compact
name: dsh-smart-compact
$ cat ~/.dsh/dsh-smart-compact/engine-state.json
{"version":"0.5.2","engineMounted":true,"auto":true,"rolloverCount":1,
"lastRolloverTrigger":"manual","budgetDegraded":false}
$ dsh plugin --profile web add dsh-smart-compact
+ dsh-smart-compact@0.5.2 (public, prebuilt)
Done in 1.6s using pnpm v11.22.0
Human command 人工命令
| Command 命令 | What it does 作用 |
|---|---|
/smart-compact | 100% 触发无缝换窗:立即开一张新窗口,旧窗口内容无损归档(零模型调用),任务连续性由 ctx_notes + ctx_history 保证。槽位被其它压缩引擎占用时该命令会明确报错(fail loud),绝不静默降级为摘要压缩。回执为人读中文:成功带 ✅ 前缀 + 新窗口号 + 归档量(首行短、防 GUI 截断),点击卡片可展开全文(含换窗后剩余预算);失败/异常带 ❌ 前缀。交接锚点诚实化(v0.3.2):换窗前若本会话从未写过 ctx_notes,新窗口锚点自动降级为「ctx_history 召回」模式(不再指向空笔记),回执同步给出 ⚠️ 提示——建议重要任务在换窗前先写笔记。 |
Composer button 一键换窗按钮(v0.4.5)
输入框工具行发送键左侧、上下文消耗表右侧固定一枚 26px 正圆按钮(琥珀金涡流 + 暗铜渐变盘),点击等同执行 /smart-compact——它把命令写入输入框并走宿主原生提交通道(与手动键入后回车完全同一条路径:adjudication → 执行 → 回执渲染全部由宿主负责)。
The composer tool row carries a 26px perfect-circle button (amber-gold vortex on a deep bronze plate) right between the context meter and the send key. One click is equivalent to running /smart-compact: it writes the command into the composer and submits through the host's own send path, so adjudication, execution, and the receipt render exactly as if you had typed it.
- 正圆保证 / Perfect circle: 几何属性全部
!important锁定,外加clip-path: circle(50%)物理裁切——宿主工具行自带的!important级按钮通配样式也压不扁(实测对抗验证)。Geometry is locked with !important and physically clipped by clip-path, immune to the host's own important row-button rules. - 草稿保护 / Draft safety: 点击时未发送文字自动保存、命令捕获后原样恢复(对照宿主 input machine 源码:bare-token 清空仅在草稿恰等于命令时发生)。Unsent text is restored verbatim after the command is captured.
- 动效 / Motion: 悬停光环 + 涡流加速旋转;点击金色冲击波 + 涡流快滚;遵循
prefers-reduced-motion。Hover glow with a faster vortex spin; click fires a gold burst and a fast roll; honorsprefers-reduced-motion. - 悬停提示 / Tooltip: 精简汉语(命令名保留英文),并随状态变化——可点时「一键换窗 = /smart-compact(归档本窗口、开新窗,草稿保留)」;刚换完窗时「窗口 #N 刚开、暂无可归档历史;发送下一条消息后可再次换窗」;槽位被占时「换窗不可用:压缩槽位由 XX 持有」。State-aware Chinese tooltip; the command name stays English.
- 语义化可用状态 / Semantic availability(v0.4.5): 刚完成换窗(新窗口内容太少)、会话全新无历史、或压缩槽位被其它引擎持有时,按钮自动置灰不可点(悬停不再发光、点击无效);内容积攒够后自动恢复。判定与压缩引擎同一条不等式:手动换窗的归档区间(retain=0,且永不收纳最新一个 surface 节点)的宿主 meter 计价必须严格大于交接检查点估价,否则引擎会以「summary is not smaller than the shadowed content」拒执(v0.4.3 曾在 dsh-android-pane 会话实测出 3 连红色无效回执:
215 ≥ 193,本版闭环——按钮在该状态直接置灰,回执也改为带数字的诚实文案)。判定由 host 半区经同源 webServer 暴露的GET /dsh-smart-compact/state?sessionId=…提供(只回窗口号 + 可归档布尔,不含任何会话内容;注册随插件 fiber 注销,卸载即净)。状态不可达(路由缺失/拉取失败)时按钮回退为始终可点(fail-open),点击行为交给命令回执兜底。 - 无新增工具注册 / No new tool registrations: 按钮不加工具、不改命令面;除上述只读状态路由外,客户端仅依赖官方 slots 服务。
- 重生成纹理 / Regenerating the texture: dsh-image-gen 生成(image-prompt
brand-identity-package模板,金色系、纯黑底)→ sips 裁 700→128 JPEG → 内联src/client/assets.ts,按钮内以mix-blend-mode: screen叠加(黑底自动消失)。The texture is screen-blended, so its black floor vanishes into the plate.
Automatic rollover 自动换窗(默认开启)
自动换窗不是按钮的附属品,而是一条宿主事件驱动的独立通路:压缩引擎注册为宿主 compaction 服务,宿主在每一步边界(agent/pre-step)与模型窗口超限恢复点(context-overflow)调用 compactIfNeeded,三带策略自动升级:
- quiet(消耗 < 85%):什么都不做;
- grace(85% ~ 85%+8192 tok):提醒一次(写 ctx_notes),继续工作;
- forced(≥ 85%+8192 tok):自动执行与手动
/smart-compact完全相同的机械换窗——零模型调用,新窗口第一条消息就是交接锚点(Context window #N opened),GUI 的「上下文已压缩」横幅照常渲染,但内容是无损换窗而非 AI 摘要。
The automatic path is host-event-driven (agent/pre-step pressure check + window-overflow recovery) and escalates quiet → grace → forced rollover — all zero-model via the template summarizer override.
提醒(25/50/75% + grace)经 system-prompt 瀑布注入模型上下文;其触达情况(briefing/tier/grace 计数与预算测量失败)自 v0.5.0 起写入 engine-state.json 的 reminderObserved——提醒静默失效从此可观测(0.4.x 时代曾发生过热会话零触达、最终被宿主 AI 摘要兜底的事故)。
Sanitization 强制脱敏(v0.5.0,无开关)
引擎自动生成的文本——交接锚点的 Task: / Last action: 两行——写入新窗口前强制过一遍脱敏:手机号、邮箱、18 位证件号、中英文企业名(按后缀识别)。命中只记数量与类别进日志(handoff anchors sanitized — N redaction(s)),绝不回显原文。
边界(刻意为之):逐字归档 ctx_history 不脱敏——本机会话的无损回查是产品核心契约;脱敏只覆盖引擎自动生成、会自动传播的面。测试与文档样例一律合成数据(张三 / 138****8000 / example.com / 示例公司)。
Engine-authored surfaces (the orientation anchors) are mandatorily sanitized before entering a new window — phone / email / national-ID / company-name patterns, counts logged, content never echoed. The verbatim archive stays untouched on purpose (lossless local recall is the core contract).
How rollover works 换窗原理(说人话)
上下文快满(默认窗口的 85%)时不再"把旧对话压成摘要",而是:先把旧窗口整体原封不动存档(本来就有的会话持久化流,零新存储)→ 留 8192 token 缓冲让模型写交接笔记 → 然后开一张干净的新窗口,窗口里只放一段固定模板("旧窗口已完整存档,当前状态在 ctx_notes,细节用 ctx_history 查")。全程不调用一次摘要模型;旧内容无损、随时可精确回读。
锚点模板按笔记实况生成:会话写过 ctx_notes → 指向笔记("从笔记继续");从未写过 → 明确声明"本会话至今没有笔记",并指示用 ctx_history(以及已落盘的项目记忆/STATE)重建上下文——绝不指向一个空笔记通道(v0.3.2 修复:手动换窗不等笔记、模板无条件指向笔记导致新窗口考古的问题)。
锚点自带任务定向行(v0.4.6):模板会附上两行机械推导(零模型调用、定长截断)的上下文——Task:(取最近一次会话标题)与 Last action:(取最新内容事件的首行);并明确声明"最新一条消息仍保留在本窗口中,归档覆盖其余全部"。前者消除"任务盲续接"(新窗口不用猜这个会话在干什么),后者防止把归档区间的 seq 稀疏误读成"收尾汇报丢失"。可用性探针按两行取最坏情况定长计价,压缩事务的"检查点必须小于待归档内容"硬不等式不受影响。
笔记新鲜度门(v0.5.2):换窗瞬间引擎把最新笔记段的时间戳与持久流对账——笔记之后还有活动(手动早换窗时必然如此:催写机制按预算阈值驱动,从不覆盖「用户随时手动换窗」)时,锚点不再无条件背书笔记,而是降级为 ⚠️ STALE 警示(最后笔记时间 + 其后活动事件数)并附机械活动尾(最近 ≤3 条工具名+时刻,只记名字绝不记参数/结果);✅ 回执同步追加「交接笔记滞后」提示行。新鲜或无法判定新鲜度时保持原锚点逐字不变;探针任何故障一律降级省略,绝不失败(真实事故实证:换窗成功但笔记落后 4 小时,旧锚点主动让新窗口信任过期状态)。
Stale-notes gate (v0.5.2): at rollover time the engine reconciles the newest notes segment stamp against the durable log. If window activity postdates the notes, the anchor downgrades to an explicit ⚠️ STALE warning plus a mechanical recent-activity tail (≤3 tool names + clocks, never arguments/results), and the success receipt carries the matching advisory. Fresh or undeterminable freshness keeps the classic wording verbatim; probe failures degrade to omission, never failure.
窗口编号口径:handoff 头里的 Context window #N 就是会话内窗口序号(第一张窗口 = #1),与 ctx_history list 打印的 window #N 同一套序号;list 额外给出 live window: #N 指明当前窗口。
Window numbering is the session-window index: the handoff header's Context window #N and ctx_history's window #N are the same numbering, and ctx_history list also prints the live window number.
When the context approaches its limit (85% by default), the engine does not write an LLM summary. Instead it archives the old window intact (the session's existing durable event stream — no new storage), grants a 8192-token grace band for the model to write handoff notes, then opens a fresh window whose anchor is a fixed template pointing at ctx_notes and ctx_history. Zero summarization calls; nothing is lost.
Configuration 配置(config.toml → 插件配置)
全部键均可选,默认值即上线行为(开箱即用):
| Key | Default | Meaning 说明 |
|---|---|---|
enabled | true | 总开关;false 时插件完全静默 |
auto | true | 自动换窗 + 提醒 + new_context;false 时仅手动 /compact 走换窗 |
thresholdRatio | 0.85 | 进入缓冲带的窗口占比 |
retainRatio | 0.16(引擎默认) | 换窗后保留的最近上下文占比;必须小于 thresholdRatio |
fallbackBufferTokens | 8192 | 缓冲带宽度;超过即强制换窗 |
maxToolOutputTokens | 8192 | 单次工具输出截断上限 |
notesDir | <dsh home>/dsh-smart-compact/notes | 笔记存储目录 |
Compatibility 兼容性
- 单槽优先原则:
compaction服务是单槽——本插件安装后默认接管该槽(rollover 换窗语义),要求在 profile bundles 里位于其它压缩引擎(dsh-glm / dsh-kiro / compaction-basic)之前。被让位的引擎(如 dsh-glm)凭其内置谦让检查自动跳过挂载,其余功能不受影响。可用状态探针确认当前模式:cat <dsh home>/dsh-smart-compact/engine-state.json(engineMounted: true= rollover 已接管;false= 槽被其它引擎占用,本插件仅提供工具与提醒)。探针同时记录自动与手动换窗:任何一次成功换窗(自动阈值触发或手动/smart-compact)都会递增rolloverCount并写lastRolloverAt,lastRolloverTrigger取值new_context/pressure/manual/context-overflow。 - 命令入口只有
/smart-compact:profile 里官方command-compact(/compact)保持disabled——避免出现"槽位被别的引擎占用时/compact静默退化为摘要压缩"这条与无损承诺冲突的路径。 - 版本要求 dsh >= 0.1.5-rc:会话日志经
Session.snapshotEvents()读取(0.1.5 之前版本的Session.events已被上游移除);宿主不提供该 API 时本插件点名报错,绝不静默失忆。 - 与第三方
toolResultPruner的隔离:该服务是可选的第三方实现,可能滞后于 dsh(实测一份陈旧副本仍在读已被上游移除的session.events,会在基础压缩内部中断每一次换窗)。因为 rollover 是整窗机械归档、保留范围由区间选择决定,前置剪枝在这里没有收益——引擎因此运行在ctx.isolate('toolResultPruner')的子上下文上,永不咨询该服务。代价:手动/smart-compact路径同样不做前置剪枝(它只在窗口真正需要压缩时才可能收敛得更少)。apply 时会自检隔离是否生效,失效则打点警告。 - 换窗请求不会被静默吞掉:
new_context请求只在换窗真正提交时才被消费;被拒(无可压缩区间)或引擎报错时请求保留在 pending 集合里,逐步骤重试,并把最近一次原因写入探针(lastRolloverRefusedAt/lastRolloverRefusedReason,相同原因 30s 内合并写入)。步骤被中止(abort)时不算拒绝:请求继续挂起,不记 ops 事件。预算不可测(如模型未配contextWindow)与换窗失败分开记账,连续 5 次触发budgetDegraded。 - Peer deps 声明随 dsh 主版本走(当前
>=0.1.5-rc <2)。插件自带的node_modules副本是运行时真实解析目标(插件的 realpath 不在宿主node_modules的解析链上),因此升级 dsh 后必须重跑npm install并重建lib/,否则会继续加载旧核心 API。 - 宿主 token-meter 容错补丁(v0.5.1 起,一次性):宿主 restore/remount 可能在旧 step 未收尾时重置 turn 计数,把会话持久流写坏——meter 重放从此永久抛错,/smart-compact、预算提醒与宿主自动压缩在该会话全部失效(本机实测 4 个会话中招)。
node scripts/patch-token-meter.mjs幂等修复(自动备份 + 语法校验,健康流零影响);每次升级 dsh 后重跑一次。补丁前的失败路径也有兜底:命令回执会点名底层错误而不是裸抛 gateway toast。
Uninstall 卸载
dsh plugin --profile web remove dsh-smart-compact
卸载即净:工具、监听器、引擎随插件 fiber 一起销毁;笔记文件保留在 notesDir(如需彻底清理请手动删除该目录)。压缩行为恢复为 DSH 默认。
Uninstall removes every tool, listener, and engine with the plugin fiber; notes files are kept under notesDir (delete manually if unwanted).
Reliability 可靠性与验收
- Vitest 123 项单元测试全绿:预算读取、笔记读写/原子性、历史回查/截断、换窗三态、new_context 消费(含防吞 fallback)、生命周期幂等、三级提醒,以及宿主 Session API 适配(含
snapshotEvents()缺失时的点名报错)、窗口编号口径、DSH_HOME隔离、pruner 隔离接线与自检、客户端按钮点击流回归与语义可用判定isInert的 fail-open/换窗后置灰/槽位被占置灰。 - 双安装形态 staging 实证:干净 staging home 里
dsh plugin add dsh-smart-compact(npm 预构建)与dsh plugin add github:hoyyang/dsh-smart-compact(源码)两种形态各自装配,--dump-config单条目无重复,boot-check [A]-[F] 六类冷启动静态检测全绿。 - 门 11 发布脱敏全绿:sanitize-check 扫工作树 + git 全历史(43+ 提交作者身份改写为公开 handle)+ 二进制清点,push 前零未处理命中。
- 客户端装配实证(v0.4.3):lib/client.js 经 tsdown 构建(30.95 kB,含内联金色涡流纹理),宿主 client-modules 表已注册(client check: lib/client.js);boot-check.sh 冷启动三故障静态检测全绿 + --dump-config 组合复检通过;正圆数值证明:敌意覆盖下 26.00×26.00 / radius 999px / clip-path circle(50%)。
- 端到端实证(staging 隔离,真实模型 glm-5.3-flash):模型依次调用
get_context_remaining→ctx_notes(写交接笔记)→new_context→ctx_history全链路成功;new_context触发真实 rollover(rolloverCount: 1),ctx_history回读到已归档窗口(window #1, ~5965 tokens archived)。 - 真机换窗实证(本机真实会话):
new_context→ 换窗提交(rolloverCount: 1、lastRolloverTrigger: new_context)→ 窗口 #1 全量归档(393 events / ~307k tokens)→ 新窗口 handoff 头为Context window #2 opened→ 跨窗ctx_history search命中 23 条;主 homeboot-check.sh冷启动静态检测全绿。 - 状态路由实机实证(v0.4.3):
GET /dsh-smart-compact/state对活跃会话回{ok:true, engineMounted:true, windowNo:2, archivable:true}(换窗后窗口 #2 且有新对话 → 可点),对新会话回{windowNo:1, archivable:false}(无可归档 → 按钮 inert);客户端经页面 fetch 拦截确认以会话真实 id 每 4s 轮询;缺失 sessionId → 400,非 GET → 405,冷会话 →no-live-agent(客户端 fail-open 保持可点)。 - 隔离转正纪律:所有装配类改动先进独立 staging home(
--dump-config组合预检 + 冷启动检测)再进主实例;改名/装配事故的复盘与防线见 git 历史与项目记忆。 - 故障排查:换窗失败先看
lastRolloverRefusedReason与命令回执的 ❌ 文案(点名底层错误);预算测不到(budgetDegraded)检查模型contextWindow配置;提醒零触达查reminderObserved.passFailures。 - 已知限制:手动 /smart-compact 路径不做前置 toolResult 剪枝(见 Compatibility 隔离条目);宿主 submit 平面在智能体运行期间拒绝提交(locked/busy refuse),运行中点击按钮为静默无操作,属宿主既定语义。
- 0.3.0 的适配依据:宿主 0.1.5-rc.1 的
Session已移除events,插件改经snapshotEvents()读取;自带的 0.1.5-rc.2 与宿主 rc.1 的运行时lib/经diff -rq校验逐字节相同。
License
BSD-3-Clause