Skip to content

dsh-toolong-warning

Verified

@hjdd14/dsh-toolong-warning · v0.1.1 · MIT · Web UI

DSH plugin: counts completed compactions per session and raises a floating reminder — with bilingual UI — only after repeated compactions plus heavy extra token spend.

Install

dsh plugin add @hjdd14/dsh-toolong-warning

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

dsh-toolong-warning

English | 中文

License: MIT dsh Node

一个 DeepSeek Harness(dsh)插件:当一段对话已经长到 不值得继续时提醒你新开对话,其余时间保持沉默。

它统计每段对话被压缩过几次,把这个数字常驻显示在右上角悬浮窗里;只有反复压缩过、并且此后 确实又烧掉大量 token 时,才会追加一行警告。

这个插件的重点在于它「不说」什么。 会喊狼来了的提醒没人会看,所以下面每条规则都写成: 一旦缺少支撑警告的依据,就宁可沉默。

悬浮提醒窗

目录

你会看到什么

右上角一个自持悬浮窗。它走 shell.overlay 插槽,拿不到该插槽时退化为自己的 fixed 容器, 所以从不与别的插件抢位置:

┌──────────────────────────────┐
│ 长对话提醒                –  │
│ 本会话已压缩 3 次            │
└──────────────────────────────┘
┌──────────────────────────────┐
│ ⚠ 本次对话过长,建议新开一个   │
│   对话                       │
│ 上下文占用已达 62%,距上次压缩 │
│ 又消耗了约 310k token。       │
│ 已成功压缩 3 次(阈值 3 次); │
│ 当前上下文占用 62%            │
│ [知道了]  [调整阈值]          │
└──────────────────────────────┘
  • 计数行始终显示;警示卡片只在规则满足时出现。
  • 知道了只隐藏当前这一次提醒,计数继续更新。
  • 调整阈值直接跳到设置页里本插件的分区。
  • 悬浮窗可拖动:按住卡片任意空白处即可移动,不必一直压着页面右上角自己的控件。松手后的位置会 记在本浏览器里,且始终留在视口内(窗口尺寸或视口变化后会被拉回来);移动过之后,设置分区里会多出 「复位悬浮窗位置」一行。
  • 插件被关闭、或没有打开会话时不显示任何东西。
  • 整个界面中英双语,语言开关就在插件自己的设置分区里 —— 见设置。

什么时候才会提醒

三条件全部成立才提醒。任何一项依据缺失就沉默。

# 条件 默认值
1 成功完成的压缩次数 —— 只有 compaction/start 配上不带 error 的 compaction/end 才算。压缩失败、正在进行中的压缩、纯裁剪(compaction/prune)都不计数。 ≥ 3 次
2 距上次压缩的额外消耗 —— 计费 token(uncachedInput + cacheRead + cacheWrite + output),由会话日志里携带的用量样本累计。 ≥ 200,000
3 或者上下文占用(下一次请求预计消耗 ÷ 模型上下文窗口)达标,或者额外消耗达到阈值的两倍 —— 后者是给未声明上下文窗口的路由留的后路,所以门槛翻倍。 ≥ 50% 或 ≥ 400,000

它会刻意保持沉默的情况

情况 结果
又长又贵,但从未压缩过 沉默 —— 只显示计数(0)
只压缩过 2 次(默认阈值是 3) 沉默
压缩过 3 次,但上下文宽裕、也没额外烧钱 沉默
压缩过 3 次、额外消耗 25 万、占用不高 —— 过了阈值但不到两倍 沉默
完全拿不到用量数据(哪怕占用 95%) 沉默
该路由没声明上下文窗口,且额外消耗很低 沉默

一个现场例子:2100+ 事件、累计约 8700 万 token、占用已到约 60% 的会话,插件什么都没说 —— 因为它从未被压缩过(gate: "compactions")。

安装

环境要求

  • dsh >= 0.1.7-rc.1(@deepseek-ai/dsh)
  • 安装者侧 Node >= 22.19(测试用到现代内置模块)
  • web profile(本插件带浏览器半,其它界面下是空操作)

1. 从 npm 安装

dsh plugin --profile web add @hjdd14/dsh-toolong-warning

2. 从 Git 仓库安装

dsh plugin --profile web add "git+https://github.com/Hjdd14/dsh-toolong-warning.git"

不需要先发布任何东西。本插件是手写 JavaScript、没有构建步骤,所以 pnpm 没有 prepare/postinstall 需要审批,安装不会卡在构建确认上。若某个 dsh 版本仍然报告有待批准的构建脚本, 把它打印出的确切键名加进 <DSH_HOME>/profiles/web/pnpm-workspace.yaml 的 allowBuilds 后重跑即可。

3. 克隆后按本地路径安装(开发用)

git clone https://github.com/Hjdd14/dsh-toolong-warning.git
dsh plugin --profile web add "link:<克隆目录的绝对路径>"

改 client.js 会在约 500 ms 内在浏览器里重载插件。改宿主侧文件(index.js、src/*.js)需要 新起一个 dsh web 进程 —— 见诊断接口。

4. 手改 profile(CLI 不可用时兜底)

在 <DSH_HOME>/profiles/web/package.json 里:

{
  "dsh": {
    "profile": {
      "bundles": ["...", "@hjdd14/dsh-toolong-warning"]
    }
  },
  "dependencies": {
    "@hjdd14/dsh-toolong-warning": "^0.1.0"
  }
}

然后在 <DSH_HOME>/profiles/web 里执行 pnpm install,再重启 dsh web。

安装打包好的 tgz

npm pack                       # 生成 hjdd14-dsh-toolong-warning-0.1.0.tgz
dsh plugin --profile web add "file:<tgz 的绝对路径>"

卸载

dsh plugin --profile web remove @hjdd14/dsh-toolong-warning

或在 dsh web 侧边栏的插件页里停用。

设置

设置页里本插件的分区包含语言开关与全部阈值。数值写入 profile 的 cordis patch,改完立即生效。

字段 默认 范围 含义
enabled true 开关 关闭后不注册状态接口,也不显示悬浮窗
compactCountMin 3 1–20 至少要成功压缩几次才可能提醒
occupancyPercentMin 50 10–95 上下文占用达到该百分比即视为"已经很贵"
tokensSinceCompactionMin 200000 10000–5000000 距上次成功压缩后又消耗多少计费 token 才算"大量多余消耗";达到该值两倍时,即使拿不到占用也会提醒
statsIntervalMs 15000 5000–120000 悬浮窗刷新间隔(页面不可见时暂停轮询)
dismissible true 开关 是否允许用「知道了」隐藏本次提醒
historyReadTtlMs 30000 5000–600000 宿主没加载的会话,其历史读取结果的复用时长
historyReadTimeoutMs 5000 1000–30000 单次历史读取的时限;超时就显示"暂时读不到该会话"

分区下半部分显示当前会话的实测值(压缩次数、累计计费 token、距上次压缩的额外消耗、上下文占用) 以及为什么现在没提醒 —— 这正是校准阈值需要的信息。

语言

同一分区里有 中文 / English 选择器。选择存在浏览器里,立即作用于悬浮窗与设置分区(无需刷新); 当 harness 的共享语言服务可写时,也会一并推送过去 —— 所以在多数部署上整个界面会跟着切换。 DSH 自带的全局语言开关在设置 → 通用里。

悬浮窗位置

直接拖动悬浮窗即可;位置存在本浏览器里,与语言选择、阈值本地覆盖同一层次,刻意不做成 profile 配置项 —— 它描述的是某个人屏幕的摆放,而不是这段对话本身。窗口始终留在视口内(视口变化、或警告卡片把窗口撑高 之后会被拉回来);只要移动过一次,这个分区里就会出现「复位悬浮窗位置」一行。

两种生效范围,以及原因

DSH 把非回环页面的配置写入固定为只读 (persistence = ctx.remote.$host.isLoopback ? 'host' : 'memory'),这对所有插件的配置表单都成立, 不是本插件的限制。与其显示一堆死控件,本插件选择诚实地降级:

打开页面的地址 三个判定阈值 enabled / dismissible
http://127.0.0.1:3080 或 http://localhost:3080(回环) 写入 profile 配置 —— 对所有会话生效 可改,写入 profile
其它地址(IP、主机名、隧道) 仍可修改,存在本浏览器,只对本页生效 只读(它们决定宿主是否注册路由)

浏览器本地的值随每次状态请求以查询参数带给宿主,宿主每次都重新校验并夹取边界,且从不落盘。 越界或非整数的值会被忽略并回报原因。判定始终发生在宿主侧,页面无法强制制造提醒。

工作原理

浏览器(client.js)                        Host(index.js + src/*)
────────────────────                       ─────────────────────────
悬浮窗                                     ctx.on('session/event')  ── 增量折叠(live)
  ├─ 会话目录 → 主视图会话 id                └─ 首次询问时回放该会话整段日志
  ├─ 每 15s 轮询 /api/.../state          ctx.sessionProjections
  └─ 计数行 + 警示卡片                      └─ tokenUsage / contextPressure
设置分区                                   ctx.sessionQuery.readSession(id) ── 历史(冷读)
  ├─ 语言开关                                └─ 宿主没加载的会话
  └─ ctx.configForms('toolong-warning')  两条只读、仅回环的路由
      8 个 volatile 字段                     /api/dsh-toolong-warning/state  (按会话)
                                             /api/dsh-toolong-warning/health (探针)

三级数据源,逐级降级

# 条件 响应 说明
1 会话在本进程内 live source: "live" 最全:占用与用量来自 meter 投影
2 未加载,但日志能读到 source: "history" 计数与消耗由日志折出 —— 这就是"切换对话不用刷新就出数字"的原因
3 两者都拿不到 known: false + reason session-not-loaded 或 history-unavailable;绝不提醒
  • 浏览器看不到会话事件日志(客户端只有会话目录),所以所有测量都在宿主侧完成、通过同源路由下发。
  • 两条路径共用同一套折叠与判定函数,因此同一会话上的计数必须相等;?selftest=1 让运行中的 进程在真实会话上自证这一点。
  • 历史源走官方支持的只读服务,不读任何私有存储路径($DSH_HOME/storages/**、会话日志文件); 也不把会话塞进 live tracker,并带 TTL 缓存与超时上限。
  • 浏览器半是纯 JavaScript,通过 dsh.client + exports["./client"] 注册,React 从 shell 模块表取; 除 React 与本包自己的 src/i18n.js 外不 import 任何东西 —— 无构建步骤、无打包依赖。
  • 宿主半除 @deepseek-ai/schemastery 外不 import 任何 @deepseek-ai/* 运行时包:设置表单要求它是 真 schema,该模块按显式候选路径加载,拿不到时退化为结构等价的描述符。

诊断接口

两条路由都只接受回环请求(socket 地址 且 Host 头 且 同源标记),响应一律 no-store。

# 插件是否激活、当前加载的版本、当前生效阈值
curl.exe -s "http://127.0.0.1:3080/api/dsh-toolong-warning/health"

# 在运行中的进程里跑自检:合成会话走完整判定链路 + 真实会话上对比 live 与 history 的计数
curl.exe -s "http://127.0.0.1:3080/api/dsh-toolong-warning/health?selftest=1"

# 某个会话的判定与原始数值。debug=1 附带事件类型统计与压缩生命周期 —— 不含任何消息内容
curl.exe -s "http://127.0.0.1:3080/api/dsh-toolong-warning/state?sessionId=<会话id>&debug=1"

state 字段:known、source(live / history)、count、lastCompactionSeq、 tokensSinceCompaction、billedTokens、occupancyRatio / projectedTokens / contextWindow、 shouldWarn、reasons、gate、thresholds、thresholdsSource、configVersion、 sampledEvents、droppedEvents、coldReads。

moduleGeneration 是承重的。 宿主侧改动只有新起一个 dsh web 进程才会加载 —— dsh 只对客户端 bundle 热重载。这个版本号存在的意义,就是让运行中的进程自报它手上是哪份代码, 而不是靠推断。

环境要求

dsh >= 0.1.7-rc.1
界面 web profile(其它界面下浏览器半是空操作)
可选 @deepseek-ai/dsh-session-query —— 启用历史源。缺失时宿主没加载的会话就是测不了(等同旧行为)
宿主依赖 @deepseek-ai/schemastery ~3.18.4,设置表单需要。注意 profile 里可解析的裸包 schemastery 是 3.18.0,没有 .volatile(),会让表单静默不可用

许可

MIT © 2026 Hjdd14