🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

会话上下文窗口 qq-24/dsh-context-window

DeepSeek Harness
qq-248 天前有提交已提供中文文档扫描:含敏感能力

给 DSH 每个对话一个独立的上下文窗口:不改…

31.3/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add qq-24/dsh-context-window
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · other
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 8 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 2 处,展示前 2 条)
  • 写入本地文件
    index.js:106
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    index.js:107
    appendFileSync(join(dir, 'dsh-context-window.log'), `${new Date().toISOString()} ${message}\n`)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
展开完整介绍(85 字)↓

给 DSH 每个对话一个独立的上下文窗口:不改 settings.yaml,覆盖值存在会话日志里,切换/重开都保留,并即时改变自动 compact 触发线与界面上下文环。

README

由 DeepSeek 最新模型翻译生成
dsh-context-window 给每个对话一个独立的上下文窗口。 不改 settings.yaml 里的模型配置,只在会话日志里存一份覆盖值:切换对话、重开 DSH、导出导入都保留,删对话即消失,并且即时改变这个对话的自动 compact 触发线,界面上的上下文环也跟着变。 它解决什么 DSH 的自动压缩触发线是一条算式: thresholdTokens = floor(contextWindow × thresholdRatio) (dsh-compaction-basic/lib/index.js:111 算阈值,:883 取窗口,:884 比较) 问题是两个因子都不是按对话给的: | 因子 | 来源 | 粒度 | | contextWindow | adapter 的 model catalog(dsh-llm-deepseek/lib/index.js:1566,默认 1e6) | provider + model,写死在 settings.yaml | | thresholdRatio | compaction-basic 插件配置(默认 0.8) | 全局,或 modelPolicies[] 按 provider+model | 于是现实里的尴尬是:1M 窗口的模型,前 800K 都不压缩。
展开完整文档(README 剩余 1 节)
dsh-context-window

给每个对话一个独立的上下文窗口。

不改 settings.yaml 里的模型配置,只在会话日志里存一份覆盖值:切换对话、重开 DSH、导出导入都保留,删对话即消失,并且即时改变这个对话的自动 compact 触发线,界面上的上下文环也跟着变。

它解决什么

DSH 的自动压缩触发线是一条算式:

thresholdTokens = floor(contextWindow × thresholdRatio)

(dsh-compaction-basic/lib/index.js:111 算阈值,:883 取窗口,:884 比较)

问题是两个因子都不是按对话给的:

| 因子 | 来源 | 粒度 |
| contextWindow | adapter 的 model catalog(dsh-llm-deepseek/lib/index.js:1566,默认 1e6) | provider + model,写死在 settings.yaml |
| thresholdRatio | compaction-basic 插件配置(默认 0.8) | 全局,或 modelPolicies[] 按 provider+model |

于是现实里的尴尬是:1M 窗口的模型,前 800K 都不压缩。 短对话被历史拖着走,token 白烧;想早点压缩,只能去改全局配置,把所有对话一起改掉。

本插件补的就是缺的那一层:会话级 contextWindow 覆盖。

把某个对话从 1M 调到 256K,这个对话就会在 256K × 0.8 ≈ 205K 开始压缩,而其它对话和模型配置一个字节都不动。

截图

完全跟随 DSH 自己的深浅色主题,不需要额外设置——插件只用 DSH 的语义色变量(--dsw-alias-)取色,切主题时由浏览器自动换值,插件不判断当前是深是浅。

桌面 · 浅色

桌面端 浅色

桌面 · 深色

桌面端 深色

手机(≤640px)

同一个座位,换成底部抽屉:遮罩、拖拽把手、× 关闭,避让 safe-area-inset-bottom,档位三列排布,触控目标 ≥40px:

手机端

安装

dsh plugin --profile web add github:qq-24/dsh-context-window

然后重启 dsh web(宿主侧代码不热载)。重启完刷新页面即可。

要求:DSH ≥ 0.1.2-rc.1,Node ≥ 22。

用法

输入框右侧的胶囊就是入口:

| 形态 | 用模型默认 | 有会话覆盖 |
| 桌面(>640px) | 上下文 默认 1.0M | 上下文 256K |
| 手机(≤640px) | ctx 1.0M | ctx 256K |

手机上刻意保留 ctx 前缀:纯数字会和 DSH 自带的上下文用量计(4/6 那种)混在一起,认不出是哪个控件。

面板里能做什么

- 滑块:16K ~ 2M,对数刻度——线性刻度下 16K~64K 会挤在最左边一丁点,对数刻度让整个行程手感均匀
- 数字输入:任意精确值,回车或点「应用」生效
- 预设档位:64K / 128K / 256K / 512K / 1M / 2M
- 用模型默认:清除覆盖,回到 adapter 那份值
- 对比行:模型默认 X · 模型名 · 当前 Y,超过模型默认时会标黄

交互约定

拖动滑块只是预览——把手位置和数字实时跟随,但不会写入;只有点「应用」或数字框回车才真正生效。这样做的原因是每帧提交一次 RPC 会把宿主刷爆,而且误触一下滑块就改掉压缩时机也太容易出事。

设计边界

这几条是产品决策,不是技术限制:

- 不限制、仅提示:允许把窗口设得比模型真实容量大。插件不做钳制,只在 UI 上标黄警告。理由:调大调小都应该由用户决定,插件替用户猜容量反而添乱。
- 不改 settings:模型默认值永远是 adapter 那份,覆盖只是会话级叠加,随时可清除。
- 可增可减:改的是 compact 判定用的窗口,不是模型真实容量,所以两个方向都生效。
- 即时生效:不需要重启,下一条消息就走新阈值。
- 随会话走:值存在会话日志里,重开、fork、导出导入都带着;删对话就跟着删,不留孤儿文件。

⚠️ 调高超过真实窗口会撞墙。 contextWindow 不只是压缩线参数,它还是 request/context 事件的记录值和 pi-ai 溢出检测的基准。设成比真实容量大,压缩永远不触发,直到 provider 返回上下文超限的 400。这是唯一会咬人的地方,所以 UI 里给了显式警告。

怎么做到的(不改 DSH 核心)

核心难点:压缩判定和界面上下文环读的是两个不同的窗口来源。

| 读者 | 读哪里 | 影响 |
| compaction-basic | llm.resolveModelInfo(...).context.contextWindow(:879) | 自动压缩在什么水位触发 |
| token-meter(界面上下文环) | request/context 事件的 data.contextWindow(:373) | 环的分母显示多少 |

只改其中一个,症状就是「压缩线动了但环不动」——用户会以为持久化坏了。所以有两条独立的接入路径。

路径一:压缩判定(AsyncLocalStorage)

agent/pre-step 是 waterfall 事件(dsh-agent/lib/types/runtime-types.d.ts:237),cordis 的 waterfall 是"每个 listener 包住链条的剩余部分"(cordis/lib/index.js:317-325):

1. 用 prepend:true 把本插件挂到 pre-step 链最前面
2. 在 listener 里 AsyncLocalStorage.run(session, () => next())
→ 后续所有 listener(含 compaction-basic)都跑在这个 async 上下文里
3. 包一层 llm.resolveModelInfo,在 ALS 上下文内把 context.contextWindow 换成会话覆盖值

compaction-basic 完全不知道自己被改了。

路径二:界面上下文环(改会话读写两端)

环的分母来自 request/context 事件,由 agent-loop 在 step 主体里写:

const contextWindow = preparedCall?.context?.contextWindow; // :757
const previousContext = session.requestContext(); // :763
if (previousContext?.contextWindow !== requestContext.contextWindow) // :764
session.append('request/context', requestContext);

preparedCall 由 llm.prepareCall() 在 :726 造出来,位置在 step 主体里。而 DSH 没有任何钩子能包住整个 step——agent/pre-step 的 next() 返回 PreStepDecision(进不进这一步),agent/request 的 next() 返回 config,两者都在 step 主体之前返回。所以 ALS 够不到 prepareCall,LlmCallConfig 里也没有 sessionId 字段可以反查会话。

只能改会话对象本身,而且必须读写两端一起改:

- session.requestContext() 读 → 返回覆盖值,让 :764 判断「变了」
- session.append 写 → 落盘前把 contextWindow 换成覆盖值

为什么不能只拦 append:preparedCall 给的值恒等于原始值,:764 判断恒为「没变化」,agent-loop 压根不调 append,拦不到任何东西。实测证据:一个 9246 事件的会话里 request/context 只有 1 条(停在会话开头)。

代价:每次请求都会多写一条 request/context(比较恒不等)。事件很小,可以接受。

持久化

写进会话日志,不碰 settings.yaml:

| 事件 | 负载 | 含义 |
| context-window/set | { window: number } | 该会话的自定义窗口 |
| context-window/clear | {} | 清除覆盖,回到模型默认 |

DSH 核心明确接受插件自定义事件类型(dsh-session/lib/types/index.js:106-108:"SessionEventMap is merge-extensible; plugin-owned events carry no core message"),envelope 校验只要求 type 是 string(:159-165)。写入时带 ignorable: true,避免被 DSH 的未知事件处理误伤。

并发安全:ALS 按 async 链隔离,主对话、子代理、goal 轮次同时 pre-step 不会串值(离线测试专门验证过)。路径二的读写补丁挂在各自的 session 实例上,天然按会话隔离。

刻意不替换官方 compaction-basic:它是 45KB 实现,含 assertNoActiveCompaction、toolPairingBalancedBefore/After、surface replaceGeneration、checkpoint 约束等不变量,且 selectCompactableRange 未从 dsh-compaction 导出。抄错的失败形态不是报错,而是「会话看起来正常、恢复时校验失败」。压缩动作始终由官方插件执行,本插件只提供窗口值。

主题适配

插件不自己存任何颜色,全部从 DSH 的语义色变量取:

text: 'var(--dsw-alias-label-primary, #1f2328)',
dim: 'var(--dsw-alias-label-secondary, var(--dsw-alias-label-dimmed, #6b7280))',
border: 'var(--dsw-alias-border-l2, var(--dsw-alias-border-l1, rgba(127,127,127,0.28)))',
surface: 'var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base, #ffffff))',
accent: 'var(--dsw-alias-state-business-primary, #4d6bfe)',
warn: 'var(--dsw-alias-state-warn-label, ...)',
danger: 'var(--dsw-alias-state-error-primary, #dc2626)',

DSH 的深浅色是这样实现的——两套 CSS 规则,切主题只是给 body 加/去掉 data-ds-dark-theme 属性:

body { --dsw-alias-bg-base: var(--dsw-static-neutral-bluish-00); }
body[data-ds-dark-theme] { --dsw-alias-bg-base: var(--dsw-static-neutral-bluish-950); }

所以插件不需要监听 theme/change、也不需要判断当前是深是浅——var() 在计算时自动取当前生效的那套值。

阴影和遮罩同样走主题 token:

- box-shadow 直接用 var(--dsw-elevation-panel) / var(--dsw-elevation-prominent)。它们本身就是「描边 + 多层投影」的完整值(形如 0 0 0 .5px #fff3, 0 3px 8px 0 #00000008, ...),不要在自己写的偏移后面拼它们——拼出来是畸形 shadow,整条声明会被 CSS 丢弃(实测 computed boxShadow 变成 none)。
手机抽屉的遮罩用 var(--dsw-alias-bg-mask-1, ...),不要写死 rgba(0,0,0,.38)——在深色主题下会脏得发灰。

验证状态

| 项 | 状态 |
| 语法检查(index / client / typert) | ✅ node --check |
| readWindow 日志解析(set/clear/穿插/非法值) | ✅ 8/8 |
| ALS + resolveModelInfo 包装(上下文内/外) | ✅ 4/4 |
| 并发隔离(两会话交错 pre-step) | ✅ 2/2 |
| waterfall prepend 语义(compaction 看到覆盖值) | ✅ 3/3 |
| apply() 冒烟(注册/patch/服务/卸载还原) | ✅ 23/23 |
| client 契约(codec / inject / call 信封) | ✅ 19/19 |
| 配置树包含本插件 | ✅ dsh web --dump-config 可见 |
| 桌面 UI 渲染 + 打开面板 + 点档位生效 | ✅ 无头 CDP 实测 |
| 手机 UI(390×844 DPR2) | ✅ 胶囊 ctx 256K、抽屉、6 档位、× 关闭 |
| 跨刷新持久化 | ✅ 二次探测直接读到上次设的值 |
| 界面上下文环跟随 | ✅ 日志实证:request/context 的 contextWindow 为设定值 |
| 滑块交互契约(拖动不提交 / 把手跟手 / 应用才生效) | ✅ 6 项断言全过 |
| 深浅色主题适配(桌面 + 手机) | ✅ 强制切两种主题,面板 bg/border/shadow/文字全部随之变化 |
| zod 解析失败的兜底(不炸启动) | ✅ 无 node_modules 目录实测 |
| compact 真实提前触发 | ✅ 真实 Web 会话写入 16K,后续 request/context 为 16K,官方 compaction/start → summary → end(success) 已观察到;触发边界受可压缩区间与固定提示开销影响 |

亲眼确认 compact 线动了

触发线的算法是 floor(contextWindow × thresholdRatio);默认 ratio 为 0.8,所以 16K 的名义阈值是 12,800 tokens。达到阈值是必要条件,但不是充分条件:官方 compaction-basic 还必须找到可压缩的历史前缀,并保留 retainTokens;system prompt、工具定义等固定开销没法靠摘要消掉。

本机真实 Web 验收结果:

- 测试会话的 context-window/set 持久化 16000(ignorable: true);压力回合及后续短回合的 request/context(seq62/79/107/133)contextWindow 均为 16000。
- 官方自动压缩真实记录了 compaction/start → compaction/summary → compaction/end 成功,证明会话覆盖已进入官方 compact 路径。
- 用当前版本生产 dsh-token-meter 对日志重放:只有约 2,574 surface tokens、保留预算 2,560 时,总压力虽已超过名义阈值,仍没有可切掉的前缀;增加一条长消息后 surface 扩大,才选中约 2,574 tokens 的历史并完成 compact。所以首次 compact 事件可以晚于 12,800,不能只凭 UI 环的整数或用户输入字符数判断阈值是否生效。
- compact 后以生产 TokenMeter.measure 重放,在这个测试会话的当前 anchor 下,保守的非-surface 压力下界约 21,624 tokens;下一 turn 的下界约 24,517,都高于 12,800。官方引擎因此又对极小 span 做了一次有界无收益尝试(summary 未变短);下一条短 turn 又观察到「成功 summary + 一次小 span 拒绝」,但原生日志有 turn/end,没有阻塞后续 prompt。此结果只证明该 session 的连续两轮行为,不代表无限重试,也不是所有会话共用的固定常数。这个 16K 测试点低于该 session 测得的当前上下文底座;真实使用应按自己的 preset、工具和注入提示调到高于该 session 的不可压缩部分。

800K × 0.8 = 640K 是模型默认窗口下的名义阈值;本次没有另做灌到 640K 的默认窗口对照,所以这里只报告公式,不把它说成已实测的对照结果。

排障

按这个顺序查,每一条都对应一个真实踩过的坑。

1. 插件装了我看不到控件

先分清是「没装上」还是「渲染成空」:

① client bundle 有没有被交付:抓首页 HTML,搜 /plugins/?? 组合列表里有没有 <包名>/client.js ② typert 端点有没有被认领(应该返回 200 且 body 是 gateway/bad-request,404 说明清单没注册)
curl -s -X POST http://127.0.0.1:3080/api/contextWindow/getState -H 'content-type: application/json' -d '{}'

2. 整个 DSH 起不来,报 ERR_MODULE_NOT_FOUND: Cannot find package 'zod'

zod 是 dsh-typert-loader 校验 codec 的硬要求(它检查 "_zod" in schema,手写 {parse} 会被拒)。Node 的模块解析是从插件文件位置往上找,插件源码不在 profile 的 node_modules 树里时就找不到。

本插件已内置多层兜底:先试常规解析,再试 $DSH_HOME 与 ~/.dsh/profiles//node_modules,全失败就退到结构等价的直通 codec(功能不受影响,只往 stderr 写一句告警)。正常情况下你应该看不到任何告警。

3. 改了窗口但界面上下文环没变

确认三件事:

1. 发消息了吗?request/context 只在窗口发生变化时才写,设完窗口得发一条消息。
2. 日志里写的是什么?解压会话日志看最后几条 request/context:

node tools/read-request-context.mjs ~/.dsh/sessions/<project>/<session>/session.jsonl.zstd

3. 补丁挂上了吗?看 ~/.dsh/diagnostics/dsh-context-window.log:

patch session: ok (append+requestContext), frozen=false

如果出现 PARTIAL appendOk=... readOk=... frozen=...,说明 Session 被冻结改不动,那是另一种修法。

4. 改 client.js 不生效 / 改 index.js 不生效

- client.js:刷新页面即可,bundle URL 带内容哈希 rev,不用重启
- index.js(宿主侧):必须重启,宿主代码不热载

重启前先看监听端口的进程真实命令行,别用裸 node bin.js web 覆盖它——本机的真实启动链是 dsh-memory-v3-runtime\dsh-web-entry.mjs web,会额外注入 --patch startup-overlay.yml。

卸载 sh
dsh plugin --profile web remove dsh-context-window

或者手动从 ~/.dsh/profiles/web/package.json 删掉 dependencies["dsh-context-window"] 与 dsh.profile.bundles 里的对应项,再 pnpm install。

完全无残留:卸载后所有会话回到模型默认窗口,日志里的 context-window/ 事件被忽略(DSH 对未知事件类型是宽容的)。

已知限制

1. 模型切换没有事件可监听。DSH 没给插件暴露 model-selection 变更事件,所以面板打开时每 2.5 秒轻量重拉一次来刷新「模型默认」对比值;面板关着时不轮询。
2. 每次请求多写一条 request/context。这是双端 patch 的代价(见上文),事件很小,但日志体积会略微增长。
3. 子代理会话:agent/pre-step 对所有 agent 生效,所以子代理也会读到自己会话的覆盖值(通常子代理没有覆盖,走模型默认,行为不变)。
4. /compact 手动命令不受影响:手动压缩不看阈值,直接压。
5. 窗口不要低于当前会话的不可压缩底座:system prompt、工具 schema、request envelope 与 retain budget 的组成依会话而异。若 floor(contextWindow × thresholdRatio) 低于该会话能达到的底座,官方 compact 仍会尝试可压缩前缀;压完若仍超线,可能再对很小的尾段尝试一次,并以「summary not smaller」结束。自动压缩不会因此拦住后续 prompt,但会增加无收益压缩尝试;应按该会话实际 prompt/tool 开销设置窗口。本机 16K 实测只是一个具体 session 的结果,不是全 profile 固定常数。

目录结构

dsh-context-window/
├── index.js # 宿主侧:ALS patch + 会话读写 patch + RPC 服务 ├── client.js # 浏览器侧:胶囊 + 双形态面板(桌面气泡 / 手机抽屉) ├── typert.js # Typert 清单:让 /api/contextWindow/ 被宿主认领 ├── cordis.patch.yml # bundle patch:一行挂进 web profile ├── docs/ # README 用的实机截图 └── package.json # dsh.bundle.patch 自挂载 + dsh.client.platform: web

License

MIT
预览分享卡片长什么样 ↓qq-24/dsh-context-window
分享 · DeepSeek Harness Hub
qq-24/dsh-context-window

给 DSH 每个对话一个独立的上下文窗口:不改…

这是你的仓库?认领条目

回到顶部