# dsh-nightshift(夜航)🌙 [English](README.md) | 简体中文 > 白天排队,错峰自动跑,跑完看账单。 [DeepSeek Harness](https://github.com/deepseek-ai/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 ``` ![dsh web 面板演示](./docs/demo.png) ## 为什么做 DeepSeek 的错峰定价(大约为 UTC+8 的夜间时段,以官方公告为准)只有高峰价的零头。但 agent 往往在*你*坐在键盘前的时段跑——恰好是算力最贵的时候。夜航把这件事反过来:现在写下任务,队列替你等到价格回落,醒来时工作已经做完,账单告诉你这份耐心值多少钱。 ## 功能 - **一键入队** —— 会话输入框旁的 🌙 芯片把当前草稿交给夜航(默认目标:当前会话续跑);「新会话」按钮改在新的会话里执行(目录跟随当前工作区并归入同一分组,未分组时用 dsh 默认目录)。 - **峰时冻结** —— 高峰时段不派发任何任务;进入高峰时已在跑的任务让它自然跑完(绝不打断已完成的工作)。 - **错峰 drain** —— 一次只跑一个,派发间隔 `drainGapMs`,冷会话由 harness 自动 resume。 - **失败策略** —— `turn/end` 结构化分类:`error`/`interrupted` → 指数退避重试(有上限);`max-tokens` → 自动发「继续」(有上限);`blocked` → 挂起等人工;`aborted` → 视为你的主动取消。 - **省钱账本** —— 逐回合 token 增量(四桶)按*发生时点*的窗口价记账,跨窗口与重试任务的花费都算得清。 - **每日报告** —— 队列清空或高峰来临时生成;同一天的二次清空合并进同一份报告,不重复计费。 - **队列持久** —— 进程重启把孤儿 running 任务重新排队(at-least-once),刷新浏览器、崩溃都不丢任务。 - **面板** —— 侧栏底入口:峰谷状态条、执行中任务、队列(立即跑/取消)、历史、最新报告。 ## 环境要求 - 启用 `web` profile 的 dsh(host 进程常驻且独立于浏览器标签页)。 - Node.js ≥ 20。 ## 安装 **官方通道(推荐)** —— dsh 自带的 plugin 管理命令,一条搞定(link + 自动加入 profile bundles,无需改任何文件): ```powershell 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 时自动降级为手动模式): ```powershell .\install.ps1 # 装 web profile .\install.ps1 -Profile headless # 装其他 profile ``` **手动降级**(没有 `dsh` CLI 时)—— 三步: ```powershell .\install.ps1 -Manual -Target "$env:USERPROFILE\.dsh\profiles\node_modules" -Profile "$env:USERPROFILE\.dsh\profiles\web" ``` 或完全手工: 1. 把本目录链接或复制到 profile 解析插件的 `node_modules`,目录名 `dsh-nightshift`(dsh 默认布局为共享的 `~\.dsh\profiles\node_modules`)。 2. 在**目标 profile** 的 `cordis.patch.yml` 追加: ```yaml - insert: - id: nightshift name: dsh-nightshift ``` 3. 重启 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/监听全部干净回收);队列在存储里不受影响。 ## 开发 ```bash npm install # 仅 devDependencies(测试工具 + 真实导入) npm test # 117 个 vitest 用例 ``` 仓库布局与完整设计契约见 [SPEC.md](./SPEC.md)。 ## 许可 [MIT](./LICENSE)