dsh-laa
DSH 插件:为每个会话提供 LAA 模式——DeepSeek 峰时自动停止运行,谷时自动继续运行。 适合可延后执行的任务,按DeepSeek峰谷计价时段暂停并恢复会话及其子代理。
설치
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:FireOpalus/dsh-laa使用
LAA 模式是每个会话各自的开关,默认关闭。
| 命令 | 作用 |
|---|---|
/laa | 查看状态:开关、当前是峰时还是谷时、下一次切换、峰时窗口、待恢复的输入数量 |
/laa on | 打开该会话的 LAA 模式 |
/laa off | 关闭;当刻就把压着的输入发出去(被中断过的轮次会先收到一条「LAA 已关闭」的续跑提示词),命令会报告发了多少、还有几个会话在等实时 agent |
/laa status | 等同于 /laa |
命令直接从 UI 作用到会话,不产生任何模型消息,因此开关本身不花 token。
Web UI 里还有第二种入口:滑动开关(轨道 + LAA 标签)。单击即切换当前会话的
模式;悬停显示当前峰谷时段、下一次切换、峰时窗口与待恢复的输入条数;开启且正处峰时
的时候轨道会变成琥珀色,一眼就能看出"它现在正被按住"。命令、开关、状态文件读的是
同一份快照,因此三者永远一致。
开关的位置跟着页面走,页面上任何时刻都恰好有一个:
| 页面 | 位置 |
|---|---|
| 已有对话的会话 | 会话页顶栏右侧(DSH 的 conversation.session.header.utilities) |
| 新对话页(还没有第一条消息) | 输入框工具行的左侧(conversation.input.left),就在权限选择与 Plan 旁边 |
DSH 在会话还没有第一条消息时会把整条顶栏藏起来(连标题带工具区),顶栏里的开关因此 根本不会挂载——这就是为什么新对话页需要第二份。它只在顶栏确实不显示时渲染,所以新 对话页、以及正在打开的会话里都点得到,可以在发出第一条消息之前就把 LAA 打开。
典型用法:晚上下班前对一个长任务说「继续做」,然后点一下那个开关(或 /laa on)——
峰时它会自己停住,谷时(工作日 12:00-14:00、18:00 之后、以及整个周末)它会自己接着做。
子会话(subagent 会话)
LAA 模式属于会话树,而不是单个会话:DSH 的 subagent 子会话(会话 header 里带
origin: 'subagent' 与 parentSession,在侧栏里嵌套显示的那些)没有自己的模式,
它跟随自己所属的顶层会话。于是父会话开着的时候子会话也开着、父会话关掉的时候子会话
立刻恢复放行,整棵树永远只有一个开关:
| 动作 | 结果 |
|---|---|
| 父会话开 LAA | 子会话也受管:峰时子会话同样被拦下,一个模型请求都不会发出 |
| 父会话关 LAA | 子会话立刻恢复放行 |
在子会话页点开关 / /laa on|off | 改的是父会话(命令会明确告诉你改的是哪个会话) |
| 子会话的悬停提示 | 多一行「跟随父会话」,写着父会话的标题与 id |
但遗留工作记在会话自己名下:峰时被拦下的输入、被中断的轮次都属于产生它们的那个 会话,谷时回到那个会话自己的 agent 上继续跑——子会话的输入不会被投递到父会话去。
峰谷切换时的动画与通知
峰谷真的翻过去的那一刻(宿主在边界后重新评估相位,前端按快照里的 nextChangeAt
对齐去问),页面上的开关:
- 跳三下(
prefers-reduced-motion下自动不播动画); - 开着 LAA 的时候,轨道颜色用 1.26 秒从绿平滑推到琥珀(或反过来)——平时 0.2 秒的 换色只够「变」,不够「看得出它变了」;
- 弹一条浏览器通知(
LAA · 进入峰时/LAA · 进入谷时),标签页在后台也收得到; 同一个会话的播报互相顶替,不会堆一屏。
通知要授权:第一次点开关时顺手申请一次(浏览器要求用户手势);拒绝授权、或者浏览器
没有这个 API,都只是没有通知,开关照常工作。播报范围由「配置」里的 notify 决定。
前端只按宿主给的边界对表,不在前端自己算第二套时间;标签页或系统休眠期间错过的切换, 由 20 秒的兜底轮询追平。
这层关系落盘在 $DSH_HOME/laa/state.json(lineage 字段,来自会话 header 的
parentSession),所以进程重启、或者子会话当前没有实时 agent 时,开关与 /laa
依旧显示父会话的那一份状态。
插件见过的子会话会随手记下;装插件之前就存在、之后一直冷着的子会话则由控制面在
第一次收到请求时,向宿主的会话清单(ctx.sessionQuery.listSessions(),也就是侧栏
在用的那一份)补一次谱系——每个进程只补一次,读失败只是退回原来的判断。
配置
编辑 profile 的 cordis.patch.yml(或直接改本包自带的 cordis.patch.yml):
- id: laa # 注意:patch 是【整体替换】config,不是逐字段合并
config:
enabled: true
timeZone: 'Asia/Shanghai'
peakWindows:
- days: [mon, tue, wed, thu, fri]
start: '09:00'
end: '12:00'
- days: [mon, tue, wed, thu, fri]
start: '14:00'
end: '18:00'
defaultMode: false
peakProviders:
- deepseek-official
cancelRunningOnPeak: true
resumeOnValley: true
tickMs: 30000
maxDeferred: 20
| 字段 | 默认 | 含义 |
|---|---|---|
enabled | true | 插件总开关。false 时插件照常加载,但任何会话都不会被停。 |
timeZone | 'Asia/Shanghai' | 峰时窗口所依据的 IANA 时区(DeepSeek 官方口径为北京时间)。 |
peakWindows | 见上 | 峰时窗口数组,其余时段一律是谷时。days 接受 mon…sun 或 1(周一)…7(周日);start/end 为 HH:MM;end <= start 表示跨零点窗口。 |
defaultMode | false | 从未被 /laa 切换过的会话是否默认开启。显式关掉过的会话不会被它重新打开。 |
peakProviders | ['deepseek-official'] | 哪些提供方路由按峰谷计费、因此受峰时约束;其余提供方在峰时不被阻塞。空数组表示谁都不拦(峰时约束失效,谷时重放语义保留)。 |
notify | 'enabled' | 峰谷切换时的浏览器通知策略:enabled(只有开着 LAA 的会话才播报)、always(任何会话都播报)、off(不播报)。只影响通知,开关上的切换动画不受影响。 |
cancelRunningOnPeak | true | 峰时开始时是否中止正在跑的轮次。 |
resumeOnValley | true | 谷时开始时是否自动续跑。 |
tickMs | 30000 | 相位重新评估的兜底间隔;窗口边界本身总是会被精确唤醒。 |
maxDeferred | 20 | 每个会话最多暂存多少条输入,超出时丢弃最旧的并记一条警告。 |
statePath | $DSH_HOME/laa/state.json | 状态文件位置。 |
官方峰谷口径
据 DeepSeek 官方定价页 注 (3): 空闲时段价格为高峰时段价格的一半,高峰时段为北京时间周一至周五 09:00-12:00、 14:00-18:00,其余为空闲时段。折算成 UTC 即周一至周五 01:00-04:00 与 06:00-10:00。
于是:工作日 12:00-14:00、每天 18:00 之后、以及整个周末都是谷时,其中从 周五 18:00 到周一 09:00 是一段连续 63 小时的谷时。
窗口写在配置里而不是写死在代码里——DeepSeek 调整过峰谷口径(2025-02-26 的
16:30-00:30 UTC 方案已被 2026-08-17 生效的新口径取代),下一次调整只需要改配置。