dsh-peak-gate · 峰谷守门 / Peak-Gate
按 DeepSeek 的峰谷分时计价,在高峰时段给 token 花销踩刹车:大任务拦下、输出上限压缩、并让模型少说废话,还提供一键放行口令。
English: Based on DeepSeek's peak / off-peak time-of-day pricing, it puts the brakes on token spend during peak hours: big tasks get blocked, output limits get compressed, and the model is told to stop waffling — plus it ships a one-word force-pass token.
⚠️ 非官方插件:与 DeepSeek / DeepSeek Harness 官方无关,是社区自研插件。 通过 web profile 的
link:依赖挂载(见下方「安装」),不是从插件市场装的。⚠️ English: Unofficial plugin: unaffiliated with DeepSeek / DeepSeek Harness; built by the community. It is mounted through a web-profile
link:dependency (see "安装 / Installation" below) and is not installed from a plugin marketplace.
安装 / Installation
这是 DSH(DeepSeek Harness)host 插件(Node half,跑在宿主侧)—— 通过 web profile 的依赖 + bundle patch 挂载: English: This is a DSH (DeepSeek Harness) host plugin (Node half, running on the host side) — mounted through a web-profile dependency + bundle patch:
- 把源码放到 profile 的 vendor 目录:
<DSH_HOME>/profiles/web/vendor/dsh-peak-gate-srcEnglish: Put the source into the profile's vendor directory:<DSH_HOME>/profiles/web/vendor/dsh-peak-gate-src - 在
profiles/web/package.json里加两处:dependencies填"dsh-peak-gate": "link:vendor/dsh-peak-gate-src";dsh.profile.bundles列表里加上dsh-peak-gateEnglish: Add two things inprofiles/web/package.json: setdependenciesto"dsh-peak-gate": "link:vendor/dsh-peak-gate-src"; and adddsh-peak-gateto thedsh.profile.bundleslist pnpm install(或按你的 DSH 版本用等价的挂载方式),然后重启dsh webEnglish:pnpm install(or the equivalent mounting method for your DSH version), then restartdsh web- 配置命名空间
peak-gate;DSH 0.1.7+ 写在profiles/web/cordis.patch.yml的peak-gate:段,热改即时生效,无需重启 English: The config namespace ispeak-gate; on DSH 0.1.7+ write it under thepeak-gate:section ofprofiles/web/cordis.patch.yml— hot edits take effect immediately, no restart needed
运行时依赖 DSH 自带的
schemastery与@deepseek-ai/dsh-llm,没有其它第三方包。English: At runtime it depends on DSH's own
schemasteryand@deepseek-ai/dsh-llm; there are no other third-party packages.
官方峰谷口径 / The Official Peak/Off-Peak Rules
来自 DeepSeek 官方定价页(核对于 2026-09-11) + 官方峰谷时间说明(2026-09-19 补充): English: Taken from the official DeepSeek pricing page (verified 2026-09-11) plus the official peak/off-peak time note (added 2026-09-19):
| 时段(北京时间) | 价格 |
|---|---|
| 周一至周五 09:00–12:00、14:00–18:00 | 高峰 ×2 |
| 其余时间、周末全天、中国法定节假日全天 | 空闲 ×1 |
English:
| Window (Beijing time) | Price |
|---|---|
| Mon–Fri 09:00–12:00, 14:00–18:00 | peak ×2 |
| all other times, the whole weekend, all Chinese statutory holidays | off-peak ×1 |
- 空闲价 = 高峰价的一半(flash 输出:谷
$0.6/Mvs 峰$1.2/M) English: the off-peak price = half the peak price (flash output: off-peak$0.6/Mvs peak$1.2/M) - 判定走「UTC+8 位移后读 UTC 字段」,不依赖本机时区(本机时区/夏令时不可信) English: the decision works by "shifting by UTC+8, then reading the UTC fields" and does not depend on the local machine's time zone (the local time zone / DST are not trustworthy)
- 🗓️ 调休上班的周末也按空闲计费(官方 2026-09-19 明确)⇒ 只看星期几,没有"补班日恢复高峰"的反向逻辑 English: 🗓️ A weekend worked as a make-up day is billed as off-peak too (stated officially on 2026-09-19) ⇒ only the day of the week is inspected; there is no reverse rule of "make-up workday ⇒ back to peak"
🗓️ 法定节假日日历(2026-09-25 新增)/ 🗓️ Statutory Holiday Calendar (added 2026-09-25)
| 层 | 说明 |
|---|---|
| ① 联网 | NateScarlet/holiday-cn(按国务院公告生成,含 isOffDay),三源按序:jsDelivr → raw.githubusercontent → gh-proxy;只拉当年,超时 8s,单份响应上限 1 MB |
| ② 磁盘缓存 | <DSH_HOME>/data/peak-gate/holidays-cn.json,默认 30 天内不重复拉;联网失败仍用旧缓存(并告警) |
| ③ 内置兜底 | 代码里的 DEFAULT_HOLIDAYS(2026 全年),离线/首启可用 |
| ④ 自检 | 日级:当年落在日历里的放假日 < 10 天(或日历项写错)⇒ 打日志告警(绝不静默按工作日判峰);每 12 小时自动核对一次 |
English:
| Layer | Description |
|---|---|
| ① Online | NateScarlet/holiday-cn (generated from State Council announcements, includes isOffDay), three sources in order: jsDelivr → raw.githubusercontent → gh-proxy; it only fetches the current year, with an 8 s timeout and a 1 MB cap per response |
| ② Disk cache | <DSH_HOME>/data/peak-gate/holidays-cn.json, not re-fetched within 30 days by default; if networking fails the old cache is still used (with a warning) |
| ③ Built-in fallback | DEFAULT_HOLIDAYS in the code (all of 2026), usable offline / on first start |
| ④ Self-check | daily: fewer than 10 holidays for that year present in the calendar (or a wrong calendar entry) ⇒ log a warning (never silently fall back to treating it as a workday when judging peak); re-verified automatically every 12 hours |
数据质量校验:单年放假日数必须在 15 ~ 45 天之间(防半截响应 / 防"全年都放假"把闸门 fail-open);
文档里不属于该年的日期一律不算数;同年数据合并是取并集(只增不减,半截响应不会覆盖完整数据);
缓存 fetchedAt 落在未来 ⇒ 判损坏并重新拉取(防"永久冻结")。
English: Data-quality validation: the number of holidays in a single year must be between 15 and 45 days (guards against truncated responses / against an "everything is a holiday" response flipping the gate to fail-open); dates in the document that do not belong to that year are never counted; merging data for the same year is a union (add-only, never shrink, so a truncated response cannot overwrite complete data); a cache whose fetchedAt lies in the future ⇒ treated as corrupt and re-fetched (guards against "permanent freeze").
🗓️ 调休上班日也算谷价日:国家标定的补班日(如 2026-10-10 周六)无条件按空闲计费 ——
与 weekendOffPeak 开关无关。缓存里的 workdays 与内置 DEFAULT_MAKEUP_DAYS 都会并进日历。
English: 🗓️ A make-up workday also counts as an off-peak day: a nationally designated make-up workday (e.g. Saturday 2026-10-10) is unconditionally billed as off-peak — independent of the weekendOffPeak switch. Both the workdays in the cache and the built-in DEFAULT_MAKEUP_DAYS are merged into the calendar.
配置项:holidays(追加的日历,默认即内置表)、useBuiltinHolidays(是否并入内置表,收窄时置 false)、
autoHolidayCalendar(关掉联网)、holidayCalendarUrls、holidayCacheMaxAgeDays、holidayFetchTimeoutMs、holidayCachePath。
English: Config options: holidays (a calendar to append, defaulting to the built-in table), useBuiltinHolidays (whether to merge the built-in table in; set it to false when narrowing), autoHolidayCalendar (turn networking off), holidayCalendarUrls, holidayCacheMaxAgeDays, holidayFetchTimeoutMs, holidayCachePath.
⚠️ 想「收窄到只认自己写的几天」:holidays: [] + useBuiltinHolidays: false + autoHolidayCalendar: false 三件一起做。
English: ⚠️ To "narrow it down to only the few days you wrote yourself": do all three together — holidays: [] + useBuiltinHolidays: false + autoHolidayCalendar: false.
⚠️ 该日历同时被右下角的余额挂件(dsh-whale-widget)读取,两边口径必须一致(挂件侧历史分桶有 2026-09-19 生效分界,插件侧没有 —— 这是有意为之)。
English: ⚠️ This calendar is also read by the balance widget in the bottom-right corner (dsh-whale-widget), and the two sides must agree on the rules (the widget's historical bucketing has a 2026-09-19 effective boundary, the plugin side does not — that is intentional).
五个着力点 / Five Levers
| # | 位置 | 干什么 |
|---|---|---|
| 1 | agent/pre-step |
峰时拦重活(默认用短提示顶替,见下);判定分三层,见下节 |
| 2 | agent/pre-step(续跑步骤) |
峰时工具预算:单轮工具步数超限就地停下,不加模型请求 |
| 3 | agent/request |
峰时把 maxTokens 压到上限 |
| 4 | systemPrompt 段 peak:brevity |
峰时要求模型精简输出 —— 拦任务是止损,让它少说废话才是从源头省输出 token |
| 5 | officialProviders 白名单 |
默认全管;填了名单就只对自家官方路由生效,不误伤中转渠道 |
English:
| # | Hook | What it does |
|---|---|---|
| 1 | agent/pre-step |
blocks heavy work during peak (by default replacing it with a short notice, see below); the decision has three layers, see the next section |
| 2 | agent/pre-step (continuation steps) |
peak tool budget: stops in place once a single turn exceeds its tool-step budget, without adding a model request |
| 3 | agent/request |
compresses maxTokens down to the cap during peak |
| 4 | systemPrompt section peak:brevity |
asks the model to output concisely during peak — blocking tasks limits the damage, but making it stop waffling is what saves output tokens at the source |
| 5 | officialProviders allow-list |
manages everything by default; once the list is filled in it only applies to your own official routes, so relay channels are not hit by mistake |
⚠️ 生死线:pre-step 在每个 step 都会跑,工具循环中间的 step 是 messages.length === 0。
判断「这是不是重活」时只在 messages.length > 0(真正有新输入)时进行,绝不把工具循环当用户输入拦;
续跑步骤只走第 2 条的工具预算,走的是一条独立的、可一键关掉(peakMaxSteps: 0)的规则。
English: ⚠️ Hard line: pre-step runs on every step, and a step in the middle of a tool loop has messages.length === 0. The "is this heavy work?" judgement is only made when messages.length > 0 (there really is new input); a tool loop is never mistaken for user input and blocked. Continuation steps go only through lever 2's tool budget — an independent rule that can be switched off with a single setting (peakMaxSteps: 0).
v2 的三层判定(2026-09-11 新增)/ v2's Three-Layer Decision (added 2026-09-11)
需求:「峰时只能解一些数学题等短的东西,或者只是问一下天气,写程序太伤了」 → 所以峰时按「这活儿重不重」决定拦不拦,而不是只看输入大小。
English: The requirement: "at peak it can only handle short things like math problems, or just asking about the weather, writing programs hurts too much" → so at peak, whether to block is decided by "how heavy is this job", not merely by how big the input is.
| 层 | 做什么 | 成本 | 兜底 |
|---|---|---|---|
| 1. 白名单直通 | 命中 lightKeywords 且输入不长 → 直接放行 |
0 | —— |
| 2. 模型分类 | 白名单没命中、输入够长 → 花一次极短调用问「heavy 还是 light」 | ~几十 token | 超时/报错/认不出 → 按 light 放行 |
| 3. 工具预算 | 峰时单轮 step > peakMaxSteps → 就地停下 |
0(不发模型请求) | 任何异常 → 放行保平安 |
English:
| Layer | What it does | Cost | Fallback |
|---|---|---|---|
| 1. Allow-list pass-through | matches lightKeywords and the input is short → pass straight through |
0 | —— |
| 2. Model classification | allow-list missed and the input is long enough → spend one extremely short call asking "heavy or light" | ~a few dozen tokens | timeout / error / unrecognisable → pass it through as light |
| 3. Tool budget | at peak, a single turn where step > peakMaxSteps → stop in place |
0 (no model request sent) | any exception → pass through and stay safe |
第 1 层刻意只判「轻」、不设黑名单:不靠词表猜「这像不像写程序」, 「重不重」交给第 2 层的模型判断,词表只用来省掉那些明显不用分类的短问句。
English: Layer 1 deliberately only recognises "light" and has no blacklist: it does not use a word list to guess "does this look like programming"; "is it heavy" is left to layer 2's model judgement, and the word list only exists to skip short questions that obviously need no classification.
第 2 层怎么调用的:走 ctx.get('llm').prepareCall()(官方公开路径且完全绕开
llm/stream 这个 waterfall),所以既不会把自己再拦一次、也不污染主请求链路。
调用时只带 provider / model / 一条 user 消息 / maxTokens: 6,并用 AbortSignal.timeout
限时;识别 a(light)/ b(heavy)两个字母,认不出就放行。
English: How layer 2 makes the call: it goes through ctx.get('llm').prepareCall() (the official public path, and it bypasses the llm/stream waterfall entirely), so it neither blocks itself a second time nor pollutes the main request chain. The call carries only provider / model / one user message / maxTokens: 6, and is time-limited with AbortSignal.timeout; it recognises the two letters a (light) / b (heavy), and passes through if it cannot tell.
⚠️ 分类调用必须关掉推理(classifierReasoningEffort: 'off',默认值):会话默认
reasoningEffort: high 会开思考,思考 token 会把 maxTokens: 6 吃光 —— 答案一个字母都
吐不出来,于是每次都走「分类未定 → 放行」,看起来像分类器根本没生效
(2026-09-11 真机实测踩到)。若某路由不支持 off,插件会自动退回默认档、并把上限放大到
32 token 重试一次。
English: ⚠️ The classification call must have reasoning turned off (classifierReasoningEffort: 'off', the default): the session default reasoningEffort: high enables thinking, and thinking tokens eat up all of maxTokens: 6 — not a single letter of the answer comes out, so every attempt lands on "classification undecided → pass through", which looks as though the classifier were not working at all (hit in a real-machine test on 2026-09-11). If some route does not support off, the plugin automatically falls back to the default level and retries once with the limit raised to 32 tokens.
第 3 层停下时长什么样:不发任何模型请求,直接把一条提示写进会话
(session.append('user/message', …),与 agent loop 自己落消息同款),界面立刻可见;
然后返回 {kind:'reject'} 结束本轮。
English: What layer 3 stopping looks like: it sends no model request at all; it writes a notice straight into the session (session.append('user/message', …), the same call the agent loop itself uses to record messages), so it is visible in the UI immediately; then it returns {kind:'reject'} to end the turn.
🩸 曾经以为「返回空 messages 就能让本轮自然收尾」—— 真机实测是错的。
dsh-agent-loop的闸门是if (turnEnds && decision.messages.length === 0) break, 而工具循环里turnEnds始终是null⇒ 空 messages 被忽略,照样发一次满历史请求、 模型继续干活(会话记录里可见:提示之后又跑了 22 次工具调用)。 宿主里唯一能真正结束本轮的就是reject(turnEnds = {kind:'blocked'}+return false)。 代价:本轮不会有turn/end,界面上本轮也没有模型收尾(提示已把原因写在会话里)。English: 🩸 It was once assumed "returning empty messages lets the turn wrap up naturally" — a real-machine test proved that wrong.
dsh-agent-loop's gate isif (turnEnds && decision.messages.length === 0) break, and inside a tool loopturnEndsis alwaysnull⇒ the empty messages are ignored, a full-history request is still sent, and the model keeps working (visible in the session record: after the notice it ran 22 more tool calls). The only thing in the host that really ends the turn isreject(turnEnds = {kind:'blocked'}+return false). The cost: this turn produces noturn/end, and there is no model sign-off in the UI for that turn either (the notice has already written the reason into the session).
生死线不是 messages.length > 0:宿主会把 source.kind === 'plugin' 的内容注入进
messages(默认启用的 dsh-repeat-tool-reminder 就会这么干),所以判据是
hasUserInput()(只要有「非插件来源」的消息才算用户的新输入),否则工具提醒会被
误当成用户请求去分类、甚至被拦截提示顶替掉。
English: The hard line is not messages.length > 0: the host injects content with source.kind === 'plugin' into messages (the default-enabled dsh-repeat-tool-reminder does exactly this), so the real test is hasUserInput() (only messages from a "non-plugin source" count as new user input); otherwise a tool reminder would be mistaken for a user request, get sent to the classifier, and even be replaced by the block notice.
拦截方式(onBlock)/ How It Blocks (onBlock)
| 值 | 行为 | 成本 | 体验 |
|---|---|---|---|
replace(默认) |
用一条短提示顶替掉这次大请求 | 省 95%+ | 用户能立刻看到「被拦了 + 怎么放行」 |
reject |
该轮直接 blocked,一个 token 都不发 |
零 | 界面静默,原因只进日志 |
English:
| Value | Behaviour | Cost | Experience |
|---|---|---|---|
replace (default) |
replaces this big request with a short notice | saves 95%+ | the user immediately sees "it was blocked + how to force it through" |
reject |
blocks the turn outright, not one token is sent | zero | the UI stays silent, the reason only goes to the log |
一键放行 / One-Word Force Pass
消息里带 !force(可配 forceToken)即无条件放行 —— 急事不用改配置。
注意:!force 整轮生效 —— 既放行这次请求,也关掉本轮的工具预算(这样长任务才跑得完)。
peakMaxSteps: 0 是彻底关掉第 3 层的开关。
English: Putting !force in a message (configurable via forceToken) passes it through unconditionally — no need to edit the config for something urgent.
Note: !force applies to the whole turn — it passes this request and also switches off this turn's tool budget (that is what lets a long task finish).
peakMaxSteps: 0 is the switch that turns layer 3 off entirely.
配置 / Configuration
命名空间 peak-gate,写在 ~/.dsh/profiles/web/cordis.patch.yml,热改即时生效,无需重启:
English: The namespace is peak-gate, written in ~/.dsh/profiles/web/cordis.patch.yml; hot edits take effect immediately, no restart needed:
peak-gate:
enabled: true
mode: block # block 真拦 | notify 只提醒 | off 关闭
peakWindows: ['09:00-12:00', '14:00-18:00'] # 北京时间
beijingOffsetMinutes: 480 # UTC+8
weekendOffPeak: true
officialProviders: [] # 空 = 所有 provider 都管;填 ['deepseek-official'] 则只管网关外的官方路由
brevitySection: true # 峰时注入「精简输出」提示段
bigTaskTokens: 6000 # 本次新输入估算超过它 = 大任务(只算新输入,不算会话历史)
bigTaskChars: 20000 # 字符数兜底
sessionPressureTokens: 0 # >0 时:会话上下文压力超它也算大任务(默认关)
# —— 第 1 层:白名单直通(只判轻,没有黑名单) ——
lightKeywords: [算一下, 计算, 求解, 解方程, 数学, 证明, 天气, 几点, 是什么, 为什么, 解释一下, 翻译, …]
lightKeywordMaxChars: 120 # 输入超过这么多字符就不认白名单
# —— 第 2 层:模型分类 ——
useModelClassifier: true # 关掉就退回「只看输入大小」
classifierProvider: '' # 留空 = 用本会话上一次请求的 provider
classifierModel: '' # 留空 = 用本会话上一次请求的 model
classifierTimeoutMs: 3000 # 超时按轻活放行
classifierMaxTokens: 6 # 只够吐一个字母
classifierReasoningEffort: off # 必须关推理:否则思考会吃光上面那 6 个 token,答案吐不出来
classifierMinChars: 20 # 短于它就不花这次调用
classifierMinTokens: 15
# —— 第 3 层:工具预算 ——
peakMaxSteps: 12 # 峰时单轮工具步数预算;0 = 关掉这条
forceToken: '!force'
peakMaxTokens: 32768 # 峰时主 agent 输出上限,0 = 不限
subagentMaxTokens: 16384
manageSubagents: true # 是否管子代理(输出上限 + 是否拦)
blockSubagents: false # 是否真拦子代理的大任务(默认不拦,免得主任务断掉)
onBlock: replace # replace | reject
warnOncePerSession: true # 仅 reject 方式有意义
dryRun: false # 只记日志不真拦
想调「多严」就动这三处:useModelClassifier(要不要模型判)、peakMaxSteps(工具预算)、
classifierMinChars(多长的输入才值得花一次分类)。
English: To tune "how strict" it is, touch these three: useModelClassifier (whether the model judges at all), peakMaxSteps (the tool budget), and classifierMinChars (how long an input must be to be worth one classification).
想看自己的 provider 叫什么?日志里每次拦截都会打印 provider=...,照着填白名单即可。
English: Want to know what your provider is called? Every block prints provider=... in the log; just copy that into the allow-list.
验证 / Verification
先跑离线自测(155 项):/ Run the offline self-test first (155 cases):
# 离线自测(155 项):依赖 DSH 提供的 schemastery / @deepseek-ai/dsh-llm,
# 所以要在 <DSH_HOME>/profiles/web/node_modules 的解析链内运行
cd <DSH_HOME>/profiles/web/vendor/dsh-peak-gate-src
node test/selftest.mjs
真机侧怎么确认插件生效(实测结论):ctx.logger 的输出不落盘(<DSH_HOME>/logs/ 里没有 peak-gate 目录),
所以别去翻插件日志,看这两处即可:
English: How to confirm on a real machine that the plugin is live (a measured conclusion): ctx.logger output is not written to disk (there is no peak-gate directory under <DSH_HOME>/logs/), so do not go digging through plugin logs — just look at these two places:
| 看哪里 | 期望 |
|---|---|
GUI「插件」页里 dsh-peak-gate 的开关 |
已启用 |
宿主启动日志 <DSH_HOME>/dsh-web-launcher.err.log |
0 字节(无加载报错) |
English:
| Where to look | What to expect |
|---|---|
the dsh-peak-gate switch on the GUI "Plugins" page |
enabled |
the host startup log <DSH_HOME>/dsh-web-launcher.err.log |
0 bytes (no load errors) |
加载成功时宿主控制台会打印
peak-gate: 峰谷守门 v2 已加载(mode=…,峰窗=…,精简段=…,白名单 N 词,模型分类=…,工具预算=… 步,自动日历=…)。
English: On a successful load the host console prints
peak-gate: 峰谷守门 v2 已加载(mode=…,峰窗=…,精简段=…,白名单 N 词,模型分类=…,工具预算=… 步,自动日历=…).
卸载 / 回滚 / Uninstall / Rollback
cd ~/.dsh/profiles/web
Copy-Item package.json.bak-peakgate-<timestamp> package.json -Force # 恢复改动前的 package.json / restore the previous package.json
pnpm install # 重新解析依赖 / re-resolve dependencies
# 然后重启 dsh web / then restart dsh web
设计参考 / Design References
写这个插件时参考了市场里几个同类插件的做法(awesome-dsh-plugin.com): English: This plugin's design references how a few similar plugins in the marketplace do it (awesome-dsh-plugin.com):
| 插件 | 借鉴了什么 |
|---|---|
dsh-peak-cost-mode |
峰时注入「精简输出」提示段(caveman 模式);systemPrompt.section 的 text 用函数动态求值 |
dsh-peak-block |
只精确匹配 deepseek-official 判定官方、避免误伤中转;时区走 UTC+8 数学换算 |
dsh-peak-indicator |
按事件时间戳分时段计价的口径意识 |
English:
| Plugin | What was borrowed |
|---|---|
dsh-peak-cost-mode |
injecting a "be concise" prompt section during peak (caveman mode); evaluating systemPrompt.section's text dynamically with a function |
dsh-peak-block |
matching only deepseek-official exactly to identify the official route, avoiding false hits on relays; doing the time zone with UTC+8 arithmetic |
dsh-peak-indicator |
awareness of pricing windows based on event timestamps |
已知限制 / Known Limitations
- 任务量是估算:按字符类型粗估(ASCII ÷ 3.6,非 ASCII × 0.75)。够分级,不适合对账。 English: Task size is an estimate: a rough estimate by character class (ASCII ÷ 3.6, non-ASCII × 0.75). Good enough for classifying, not for accounting.
- 模型分类要多花一次调用:每次判定 heavy/light 约几十 token(
maxTokens: 6)。相比拦下一次 写程序的工具循环(动辄上万 token)可以忽略,但它确实是「每条够长的输入都问一次」的成本模型; 想省就调高classifierMinChars/ 调低useModelClassifier。 English: Model classification costs one extra call: each heavy/light decision is about a few dozen tokens (maxTokens: 6). Next to blocking one program-writing tool loop (easily tens of thousands of tokens) that is negligible, but it really is a cost model of "ask once for every input that is long enough"; to save, raiseclassifierMinChars/ turn downuseModelClassifier. - 停下的那一轮没有模型收尾:第 3 层是「不发请求、直接落提示」,所以用户看到的是进度停住 +
一条说明,而不是模型自己写的总结。要继续就带
!force重发,或把peakMaxSteps调大。 English: The turn that gets stopped has no model sign-off: layer 3 is "send no request, drop a notice instead", so what the user sees is progress halting plus an explanation, not a summary written by the model. To carry on, resend with!force, or raisepeakMaxSteps. reject方式界面静默:被拦的消息不进会话记录,会看到"发出去没反应"。默认的replace没这问题。 English: Therejectmode is silent in the UI: the blocked message does not enter the session record, so it looks like "sent but nothing happened". The defaultreplacedoes not have this problem.- 不做记账:目前只在日志里记拦截次数,没有「今天省了多少钱」的面板。要做需要订阅
llm/stream采真实 usage + 落盘 + client 半边(市场里dsh-peak-cost-mode是完整形态,可参考)。 English: No accounting: for now it only records the number of blocks in the log; there is no "how much did I save today" panel. Building one would mean subscribing tollm/streamto sample real usage + persisting it + a client half (marketplace plugindsh-peak-cost-modeis a complete form of this, worth referencing). - 精简段会让 KV cache 失效一次:峰谷切换时 system prompt 变化,会丢一次前缀缓存。一天只切 4 次,可接受。 English: The brevity section invalidates the KV cache once: switching between peak and off-peak changes the system prompt, so one prefix cache is lost. It only switches 4 times a day, which is acceptable.
- 只认 DeepSeek 官方 API 的计价口径;走第三方网关时用
officialProviders排除,或自己改peakWindows。 English: It only recognises the pricing rules of the official DeepSeek API; when going through a third-party gateway, exclude it withofficialProviders, or changepeakWindowsyourself.
运行环境 / Requirements
- DSH(DeepSeek Harness)≥
0.1.7-rc.2——package.json里dsh.compatibility.dshReleases声明为compatible;本仓在该版本上实测通过。 - Node.js ≥ 20 ——
package.json的engines.node;本机在 Node 24 上实测通过。 - 运行依赖:无。代码用到
schemastery与@deepseek-ai/dsh-llm,两者均由 DSH 宿主提供(见peerDependencies),本仓不随包安装任何第三方运行依赖。 - 可选联网:默认会去取法定节假日日历(可关,见「配置」里的
autoHolidayCalendar);关掉后走内置兜底表。
English:
- DSH (DeepSeek Harness) ≥
0.1.7-rc.2— declaredcompatibleinpackage.json'sdsh.compatibility.dshReleases; verified on that release. - Node.js ≥ 20 — see
engines.nodeinpackage.json; verified on Node 24 here. - Runtime dependencies: none. The code uses
schemasteryand@deepseek-ai/dsh-llm; both are provided by the DSH host (seepeerDependencies). - Optional networking: by default it fetches the statutory-holiday calendar (can be turned off via
autoHolidayCalendarunder "Configuration"); with networking off it falls back to the built-in table.
权限与依赖 / Permissions & Dependencies
本节如实描述本插件对系统的接触面,按「保守」口径写:没查到的地方不写成"不访问"。 English: This section describes, in a deliberately conservative way, what this plugin touches on the system. Where something could not be verified, it is not claimed to be "not accessed".
这是 DSH(DeepSeek Harness)宿主插件:它是 Node half,跑在 dsh web 宿主进程内,通过 profile 的 bundle patch 挂进来,不单独起进程、不开端口、不注册系统服务。因此它与宿主同权限 —— 宿主能读写的,它理论上也能;下面列的是它代码里实际会碰的部分(代码可逐行审计)。
English: This is a DSH (DeepSeek Harness) host plugin: it is a Node half that runs inside the dsh web host process, mounted through a profile bundle patch. It starts no separate process, opens no port and installs no system service. It therefore shares the host's privileges — anything the host can read or write, it could in principle too; listed below is what its code actually touches (the code is short enough to audit line by line).
| 接触面 | 实际情况 |
|---|---|
| 文件(读) | 读 DSH 的插件配置(由宿主 settings 服务提供,命名空间 peak-gate)与节假日缓存;不主动扫描/读取你的工作文件。 |
| 文件(写) | 只写节假日缓存一个文件:默认 <DSH_HOME>/data/peak-gate/holidays-cn.json(可用 holidayCachePath 改到别处)。 |
| 网络 | 默认开启节假日日历更新:只按顺序请求三个源(jsDelivr → raw.githubusercontent → gh-proxy),只拉当年的日历 JSON,单源超时 8 s、响应上限 1 MB。可用 autoHolidayCalendar: false 或 holidayCalendarUrls: [] 完全关掉联网。除此之外不发起任何网络请求。 |
| 外部命令 | 不调用任何外部命令 / shell / 子进程;不需要 root / 管理员权限。 |
| 凭据 | 不读取、不存储任何凭据(不读环境变量里的 API key、不读凭据文件)。第 2 层的分类调用复用宿主已经配置好的 LLM 路由,鉴权由 DSH 负责;日志里可能打印 provider / model 名,不含密钥。 |
| 生命周期脚本 | 无:package.json 没有 scripts,没有 postinstall 之类的安装期脚本。 |
| 已知风险 | ① 峰时它会主动改变你的请求(默认用一条短提示顶替大请求)⇒ 先用 dryRun: true 或 mode: notify 观察;② 节假日日历来自第三方镜像源,插件已做质量校验(15~45 天、年份归属、并集合并、1 MB 上限、时间戳未来判损坏),但不能保证第三方源不被篡改;③ 分类与"重活"判定是启发式的,可能误判(设计上宁可放行);④ 它跑在宿主进程内,与宿主同权限。 |
English:
| Surface | What actually happens |
|---|---|
| Files (read) | Reads the DSH plugin config (supplied by the host settings service, namespace peak-gate) and the holiday cache; it does not scan or read your project files. |
| Files (write) | Writes exactly one file: the holiday cache, by default <DSH_HOME>/data/peak-gate/holidays-cn.json (relocatable via holidayCachePath). |
| Network | Holiday-calendar updates are on by default: it requests three sources in order (jsDelivr → raw.githubusercontent → gh-proxy), fetching only the current year's calendar JSON, with an 8 s per-source timeout and a 1 MB response cap. Networking can be turned off entirely with autoHolidayCalendar: false or holidayCalendarUrls: []. It makes no other network requests. |
| External commands | Calls no external command, shell or subprocess; requires no root / administrator privileges. |
| Credentials | Reads and stores no credentials (no API keys from environment variables, no credential files). Layer 2's classification call reuses the LLM route the host is already configured with; authentication is DSH's business. Logs may print provider / model names, never secrets. |
| Lifecycle scripts | None: package.json has no scripts field and no postinstall-style install hooks. |
| Known risks | ① During peak it actively alters your requests (by default replacing a big request with a short notice) ⇒ observe it first with dryRun: true or mode: notify. ② The holiday calendar comes from third-party mirrors; the plugin validates it (15–45 days, year ownership, union merge, 1 MB cap, future timestamps treated as corrupt) but cannot guarantee a third-party source is untampered. ③ The classifier / "heavy work" decision is heuristic and can misjudge (by design it prefers to let things through). ④ It runs inside the host process and therefore shares the host's privileges. |
No comments yet. Be the first to write one.