본문으로 건너뛰기
L

dsh-context-radar

liujianqiao701/dsh-context-radar

Estimates what is filling the context window — per instruction file, per skill and per tool result — with a bloat verdict and actionable suggestions.

설치

dsh plugin --profile web add github:liujianqiao701/dsh-context-radar

README

dsh-context-radar

Context bloat detector and per-document attribution for DeepSeek Harness.

DSH already tells you how full the context window is. It does not tell you what filled it. This plugin answers both:

QuestionAnswer
Is my context bloated?Multi-factor score (occupancy / growth rate / single-item dominance / dead weight) → 🟢 🟡 🟠 🔴 + concrete actions
Which document eats the most?Ranked table of every system-prompt section, every AGENTS.md-style file, every skill, every tool result — with % of used and % of window
Why was my instructions file truncated?It reads DSH's own budget marker and reports exactly how many bytes were dropped, and from which file

Zero hard dependencies. Every DSH service is optional and probed at runtime — if something is missing, the report loses a section instead of throwing.


中文说明

它解决什么问题

DSH 自带的 tokenUsage / contextPressure / contextBreakdown 三个投影只给出三路聚合 (system / tools / message)。你看到「消息占 82%」,但不知道是哪五个文件、哪几条工具结果填出来的。 本插件把这一层补上,并且额外回答一个 DSH 不回答的问题:现在是不是该压缩了。

三件事

  1. 归因——把上下文逐项列出来,每项给 token 数与两个百分比:

    • 占已用:在已经吃掉的空间里占多少 → 回答「谁最该砍」
    • 占窗口:在总窗口里占多少 → 回答「还剩多少余量」 两个分母不同,答案也不同,所以两列都给。
  2. 判定——用四因子加权打分,不是拍脑袋阈值:

    因子权重含义
    上下文占用率45%已用 / 模型窗口(有提供方报告的压力时以它为准)
    增长速率20%最近 3 轮平均新增,折算「还能撑几轮」
    单点垄断度20%最大单项占已用比例,>25% 说明有「一个怪物」
    冗余 / 死重15%同一文件被重复读取累计的 token
  3. 告警——几条不看分数、命中即报的硬事实:

    • 🔴 工作区指令被截断(AGENTS.md 超字节预算)→ 精确报出丢了哪个文件、丢了多少字节、丢了几成
    • 🔴 有指令文件被整体丢弃
    • 🟡 单个工具结果 ≥ 已用 15%
    • 🟡 某个 skill 正文 ≥ 已用 8%

一个必须说明的口径问题

DSH 官方计价是「4 个字符 = 1 token」,这是英文密度。中文实测约 1 字符 ≈ 0.6~1 token, 所以官方数字对中文内容低估 3~4 倍(DSH 自己的文档也承认 CJK 与 JSON schema 被严重低估)。

本插件因此提供两套口径:

  • cjk*(默认)—— 中文按 1 字符 1 token(保守),英文按 4 字符 1 token,用于判膨胀
  • dsh* —— 与 DSH 官方逐字节一致,仅用于对照

为什么不能只用官方口径:占比是等比缩放的,低估无所谓;但判膨胀时低估是致命的 —— 明明已经 90%,按 4 字符/token 算出来只有 25%,于是永远不告警。

报告里会明确标出「数字约为 DSH 官方口径的 N 倍」,所以不会和 DSH 面板对不上时产生困惑。

三个入口

入口用途
工具 context_radar模型按需查(limit 控制行数,probes 附带采集诊断)
/ctx · /ctx-watch你自己看,不消耗模型回合;/ctx --all 看全量
系统提示词段落只在超阈值时注入一行提醒;健康时返回空串,零开销
GET /api/dsh-context-radar/reportGUI 面板的数据接口(JSON)

数据从哪来(不猜,全有出处)

⚠️ 先记一条实测坑:DSH 的事件有两种形状 —— user/message 的 data 本身就是消息 (data.source / data.content),assistant / tool/result / system 才是 data.message。 只认一种会静默丢掉全部按文档归因。

⚠️ 第二条实测坑:不能遍历原始事件日志。DSH 是事件溯源的,压缩不删日志 —— 被压缩掉的历史仍在日志里,直接遍历会算出物理上不可能的占用(实测 2751%)。 所以归因吃的是 session.surface.deriveMessages()(模型真正看到的那份)。

层DSH 事实来源拿到的粒度
绝对占用assistant/message 的 data.usage.totalTokens(= input + cacheRead + output)提供方账单口径的真值,优先于一切估算
会话画布session.surface.deriveMessages()模型实际看到的冻结消息数组
系统提示词system/message 的实际总量 + systemPrompt.assemble() 各段字符占比逐段(总量用实际消息,各段只负责分摊)
工作区指令user/message 的 data.source.kind === 'agent-instructions'逐文件(按 Instructions from: <path> 分节)
运行时上下文data.source.kind === 'runtime-context' 且 source.sections[]逐段(沙箱策略等)
skill 目录source.kind === 'skill-catalog' 且 source.entries[]逐 skill
skill 正文source.kind === 'skill-invocation' 且 source.name逐 skill
工具结果tool/result → message.source.callId → assistant/message 的 tool-call 块工具名 + 目标文件/命令
真实窗口ctx.llm.resolveModelInfo(provider, model).context模型真实上限
投影压力contextPressure 投影的 projectedTokens账单拿不到时的次选
字节预算注入正文里的 Workspace instruction budget N bytes: …预算 + 截断/丢弃明细

安装

# 已经在本机:把 <插件目录> 换成你的路径
dsh plugin --profile desktop add link:<插件目录>

或者手工两步(等价):

  1. profile 的 package.json 里加依赖与 bundle:
    {
      "dependencies": { "dsh-context-radar": "link:D:/path/to/dsh-context-radar" },
      "dsh": { "profile": { "bundles": ["...", "dsh-context-radar"] } }
    }
    
  2. 在 <profile>/node_modules/ 下建到插件目录的联接(Junction / symlink)。

改动后必须重启 DSH 才会加载。

已知限制

  • token 数是估算,不是分词器实测值。中文取 1 字符 1 token 是有意偏保守(宁可早报,不可漏报); 要更贴近实测可改 lib/estimate.js 的 DENSITY.cjk(0.6 更接近 DeepSeek 分词器)。
  • 归因覆盖不了「无法逐条归因」的部分(适配器内部开销等)。这种情况会单列一条「未归因」, 保证各项之和严格等于提供方账单,两个百分比列的口径一致、可以互相验算。
  • 拿不到模型窗口时(llm 服务缺失或不知道 provider/model),只给构成、不给膨胀结论 —— 不猜。
  • 「工具定义」一项按最近一次请求的工具清单算。会话中途工具集变了(MCP server 挂载/卸载、 跨会话恢复),要等下一次请求才反映出来。

二、踩过的 DSH 契约坑(写给维护者)

这些全部是实机才暴露的,写完照样有过 46 项测试全绿的版本 —— 假数据照着错假设编,测试再绿也没用。 改这个插件前请先读一遍:

契约规则写错的后果
工具 parameters必须是完整 JSON Schema:{ type: 'object', properties, required }平铺属性 → 网关报 schema must be of 'type: "object"', got 'type: null' → 整个会话的消息都发不出去
工具 output.schemarequired 放在对象层(数组),不能写进每个属性报 required is not supported on type "string" → 工具静默注册不上
命令定义字段是 handler(不是 run);收到一个 invocation 对象 { commandId, agent, rawInput, attachments, signal };必须返回 { kind: 'success'|'error', text }报 handler must be a function / 返回值被拒收 → 命令静默消失
命令名必须匹配 /^[a-z][a-z0-9_-]*$/u,description 非空注册即抛错
服务挂载时序apply() 执行时 tools / webServer 还没挂载(commands / systemPrompt 已就绪)直接取到空 → 工具与路由静默不注册,插件"装了跟没装一样"
模型窗口llm.resolveModelInfo(p, m) 返回 { context: { contextWindow } } —— 嵌套按 info.context 当数字取 → typeof 判定失败 → 窗口永远 null
事件形状user/message 的 data 本身就是消息;assistant / tool/result / system 才是 data.message只认一种 → 按文档归因全部落空,还不报错
会话画布压缩不删日志,必须吃 surface.deriveMessages()遍历原始事件 → 算出物理上不可能的占用(实测 2751%)
绝对用量锚在 assistant/message 的 data.usage.totalTokens纯本地估算 → 与实际账单差一个量级

License

MIT

관련 플러그인