dsh-nightshift(夜航)🌙
English | 简体中文
> 白天排队,错峰自动跑,跑完看账单。
DeepSeek Harness(dsh)第三方插件。白天高峰时段任务在队列里冻结不花钱;低谷时段一到,夜航把它们逐个派发进会话——失败自动退避重试、max-tokens 自动「继续」、按峰谷单价把真实 token 消耗记账。队列清空(或高峰来临)时生成当日省钱报告。
白天(高峰) 夜间(低谷)
┌────────────────────┐ ┌────────────────────┐
│ 🌙 等低谷 2小时13分 │ ───▶ │ 🚀 夜航中 8小时40分 │
│ 已排队 3 个任务 │ │ ▶ 任务 1/3 已完成 │
│ (冻结,不花钱) │ │ ▶ 任务 2/3 执行中 │
└────────────────────┘ └────────────────────┘
2026-09-01 报告:
共 12 个 · 8.4M tokens
实际 ¥8.40 · 省下 ¥8.40
为什么做
DeepSeek 的错峰定价(大约为 UTC+8 的夜间时段,以官方公告为准)只有高峰价的零头。但 agent 往往在你坐在键盘前的时段跑——恰好是算力最贵的时候。夜航把这件事反过来:现在写下任务,队列替你等到价格回落,醒来时工作已经做完,账单告诉你这份耐心值多少钱。
功能
- 一键入队 —— 会话输入框旁的 🌙 芯片把当前草稿交给夜航(默认目标:当前会话续跑);「新会话」按钮改在新的会话里执行(目录跟随当前工作区并归入同一分组,未分组时用 dsh 默认目录)。
- 峰时冻结 —— 高峰时段不派发任何任务;进入高峰时已在跑的任务让它自然跑完(绝不打断已完成的工作)。
- 错峰 drain —— 一次只跑一个,派发间隔
drainGapMs,冷会话由 harness 自动 resume。 - 失败策略 ——
turn/end结构化分类:error/interrupted→ 指数退避重试(有上限);max-tokens→ 自动发「继续」(有上限);blocked→ 挂起等人工;aborted→ 视为你的主动取消。 - 省钱账本 —— 逐回合 token 增量(四桶)按发生时点的窗口价记账,跨窗口与重试任务的花费都算得清。
- 每日报告 —— 队列清空或高峰来临时生成;同一天的二次清空合并进同一份报告,不重复计费。
- 队列持久 —— 进程重启把孤儿 running 任务重新排队(at-least-once),刷新浏览器、崩溃都不丢任务。
- 面板 —— 侧栏底入口:峰谷状态条、执行中任务、队列(立即跑/取消)、历史、最新报告。
环境要求
- 启用
webprofile 的 dsh(host 进程常驻且独立于浏览器标签页)。 - Node.js ≥ 20。
安装
官方通道(推荐) —— dsh 自带的 plugin 管理命令,一条搞定(link + 自动加入 profile bundles,无需改任何文件):
dsh plugin --profile web add github:mikasaxin529/dsh-nightshift
装到别的 profile 换名字即可(headless 等);要锁版本可加 commit 号(github:mikasaxin529/dsh-nightshift#);卸载用 dsh plugin --profile web remove dsh-nightshift。装完重启 dsh 生效。本地 clone 的仓库把 github: 换成路径即可。
脚本 —— 在本仓库目录下(内部走官方通道,dsh 不在 PATH 时自动降级为手动模式):
.\install.ps1 # 装 web profile
.\install.ps1 -Profile headless # 装其他 profile
手动降级(没有 dsh CLI 时)—— 三步:
.\install.ps1 -Manual -Target "$env:USERPROFILE\.dsh\profiles\node_modules" -Profile "$env:USERPROFILE\.dsh\profiles\web"
或完全手工:
- 把本目录链接或复制到 profile 解析插件的
node_modules,目录名dsh-nightshift(dsh 默认布局为共享的~\.dsh\profiles\node_modules)。 - 在目标 profile 的
cordis.patch.yml追加:
- insert:
- id: nightshift
name: dsh-nightshift
- 重启 dsh(或让 loader 热应用补丁)。
插件刻意与宿主共享同一份 @deepseek-ai/* 实例(因此声明为 optional peerDependencies——自带副本会破坏 cordis 服务身份识别)。宿主侧不要在插件目录里 npm install 依赖;devDependencies 只为本地跑测试。
配置
全部配置项在 loader config 的 nightshift 条目下(含默认值):
| 键 | 默认 | 含义 |
|---|---|---|
timeZone |
Asia/Shanghai |
峰谷窗口解释所用的 IANA 时区 |
peakWindows |
09:00–12:00, 14:00–18:00 |
[start, end) 墙钟窗口;可跨午夜;[] = 永不高峰 |
peakPricePerMTok |
2 |
高峰每百万 token 单价(请填真实数字) |
offPeakPricePerMTok |
1 |
低谷每百万 token 单价 |
currency |
¥ |
仅展示用 |
tickMs |
30000 |
窗口检查 / 派发节拍 |
maxRetries |
3 |
error/interrupted 回合的重试上限 |
retryBaseMs / retryFactor / retryMaxMs |
60000 / 2 / 1800000 |
指数退避基数、因子、封顶 |
continuationLimit |
3 |
每任务自动「继续」次数上限 |
continuationPrompt |
继续 |
续跑提示词 |
drainGapMs |
5000 |
派发间隔 |
allowRunNow |
true |
是否允许面板「立即跑」 |
reportRetentionDays |
30 |
报告保留天数 |
exposeTool |
false |
v1.1 预留(nightshift_enqueue 工具) |
工作原理
- host 半(
index.js)是 cordis 函数式插件:一个自递归setTimeout节拍(绝不用setInterval)、一个监听turn/end的session/event处理器、两个 GET exact-fetch 路由(/api/nightshift/state、/api/nightshift/report)与三个 POST webServer 路由(enqueue、task/cancel、task/run-now,均经 connection 鉴权)。队列与报告经ctx.storageDomain持久化(zod 行 schema)。 - client 半(
client.js)是手写的window.__ModuleLoader__.load工厂——classic script,注册两个 slot:输入框芯片(conversation.input.dock)与侧栏面板(sidebar.footer.action)。轮询 state 路由(面板开 30s、关 2min),不持有任何 reload 后无法重建的 UI 状态。 - 展示格式化在
lib/format.js(client 工厂触不到 host 模块图,语义在其内重新内联实现)。
与相邻插件的差异
- 对比 sleep-send / 定时任务:它们在你指定的时间发送;夜航跟随价格窗口——窗口配一次,任务不用逐个配时间,改配置即整体冻结/解冻队列。
- 对比 session-guard / input-traffic:它们塑造或管控交互输入;夜航不碰你的实时会话。冻结 = 不派发,不是会话被冻结——任何时段手动照常干活。
已知限制
- cache 计价简化:四桶 token 同价计费(cache 读比 DeepSeek 真实定价偏贵,因此省钱数字在该维度上偏保守)。
- at-least-once:任务执行中崩溃会在重启后重跑;任务本身是否幂等由你把关。
- 单并发——v1 有意为之的不变量。
- 单价是你填的数字;夜航没有余额/定价 API 可以核对。
- 改配置触发 HMR 重载(timer/domain/监听全部干净回收);队列在存储里不受影响。
开发
npm install # 仅 devDependencies(测试工具 + 真实导入)
npm test # 117 个 vitest 用例
仓库布局与完整设计契约见 SPEC.md。