dsh-schedule-view
已验证@lijian-ui/dsh-schedule-view · v0.1.1 · MIT · Web 界面
Cron-based scheduled task plugin for DeepSeek Harness (dsh): pure UI management, zero LLM tools, cross-session timer, multi-level notifications.
安装
dsh plugin add @lijian-ui/dsh-schedule-view 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
标签
说明文档
dsh-schedule-view · 定时任务插件
English | 中文
为 DeepSeek Harness (dsh) 桌面端提供基于 cron 的定时任务功能:在设置面板中创建 / 编辑 / 删除 / 立即执行任务,支持跨 session 的 agent follow-up 和多级通知。零 LLM tool —— 纯人驱动的调度。
功能概览
| 功能 | 说明 |
|---|---|
| 7 种调度类型 | 间隔 / 每天 / 每周 / 每月 / 每年 / 一次性 / cron |
| Cron 表达式 | 完整 5 字段 cron,实时校验 + 人类可读预览("每个工作日 09:00") |
| 跨 session 触发 | Timer 在 host 进程级存活,目标 session 关闭也能触发 |
| Agent follow-up | 到期以 user role 注入提示词(请根据系统指令开始执行任务。),AI 立即执行 |
| 执行生命周期 | 追踪 delivered → running → completed/failed,记录 AI 回复摘要 |
| 多级通知 | 页面 toast(8s 自动消失)+ WebAudio 提示音(零文件)+ Electron 桌面通知 + 未读徽标 |
| 模型选择 | 每个任务可指定 provider/model,未指定则用部署默认 |
| 工作目录 | 每个任务可绑定 cwd,上下文感知执行 |
| 错过处理 | 跳过错过窗口,或下次启动补跑一次(应用重启 / 休眠场景) |
| 零 LLM Tool | 不注册任何 LLM tool,零 schema 开销,纯 UI 驱动 |
背景
dsh 官方 @deepseek-ai/dsh-schedule 插件存在以下局限:
| 局限 | 官方 | 本插件 |
|---|---|---|
| 调度粒度 | after_seconds / at / every_seconds |
完整 cron + 7 种类型 |
| Session 范围 | 仅 session 本地 | 跨 session(host 级 timer) |
| 通知 | 仅对话内 | toast + 提示音 + 桌面通知 + 徽标 |
| UI 管理 | 无(纯 LLM tool) | 完整设置面板 |
| LLM schema 开销 | 3 个 tool(schedule_create/list/delete) |
零 tool |
安装
前置条件
- DeepSeek Harness (dsh) 桌面端
- Node.js >= 18
安装
dsh plugin add @lijian-ui/dsh-schedule-view
本地开发
# 进入插件目录
cd extensions/dsh-schedule-view
# 安装依赖
npm install
# 构建
npm run build
# 监听模式
npm run watch
# 类型检查
npm run typecheck
构建产物在 lib/ 目录下,通过 junction 自动同步到 node_modules/@lijian-ui/dsh-schedule-view。每次构建后需重启桌面端加载新 bundle。
使用方式
- 打开 dsh 桌面端
- 进入 设置 → 定时任务
- 在任务列表中:
- 点击 新建任务 创建定时任务
- 点击滑块按钮启用/停用
- 点击 编辑 修改调度规则、提示词或模型
- 点击 立即执行 手动触发一次
- 点击 删除 永久移除
- 点击 执行历史 查看运行记录
任务字段
| 字段 | 必填 | 说明 |
|---|---|---|
| 任务名称 | 是 | 任务名 |
| 调度规则 | 是 | 7 种类型:间隔 / 每天 / 每周 / 每月 / 每年 / 一次性 / cron |
| 提示词指令 | 是 | 到期注入目标 agent 的指令 |
| 目标 Session | 是 | 接收提示词的 session |
| 工作目录 | 否 | 任务 agent 绑定的 cwd(绝对路径) |
| 执行模型 | 否 | provider/model 覆盖,未指定则用部署默认 |
| 时区 | 否 | cron 解析用的本地时区(默认系统时区) |
调度类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 间隔 | 每 60 分钟 | 固定分钟间隔 |
| 每天 | 每天 09:00 | 壁钟时间 |
| 每周 | 周一、三、五 09:00 | 选择星期 |
| 每月 | 每月 1 号 09:00 | 每月第几天(1-31 或最后一天) |
| 每年 | 每年 1 月 1 日 09:00 | 月份 + 日期 |
| 一次性 | 2026-09-01T09:00:00 | 单次触发,触发后自动停用 |
| Cron | 0 9 * * 1-5 |
完整 5 字段 cron 表达式 |
错过处理
应用关闭或休眠时错过了触发窗口:
| 策略 | 行为 |
|---|---|
| 跳过 | 丢弃错过窗口,等待下次调度时间 |
| 补跑一次 | 下次 tick 时补跑一次(一次性任务默认) |
执行生命周期
任务触发时:
delivered → running → completed/failed
| 阶段 | 触发条件 | 处理 |
|---|---|---|
| 已投递 | agent.followup 成功 |
创建历史记录,发送桌面通知 |
| 执行中 | user/message 事件匹配注入的 messageId |
捕获 AI 回复摘要,记录耗时 |
| 已完成 | turn/end 事件 |
最终状态、endReason、toast + 提示音通知 |
| 已失败 | turn/end 含错误 |
标记失败,持久 toast,错误提示音 |
| 已跳过 | 触发时 agent 不 live | 不 followup,跳过通知,历史标记 skipped |
技术架构
目录结构
extensions/dsh-schedule-view/
├── src/
│ ├── index.ts # Host 入口(安装设置面板 + 启动 timer)
│ ├── remote.ts # Host RPC 方法(list/create/update/delete/fireNow)
│ ├── timer-runtime.ts # 核心 timer 引擎(cron 解析 + tick 轮询 + 触发)
│ ├── schedule-core.ts # 调度计算(下次触发时间)
│ ├── lifecycle-tracker.ts # Session/event 监听器,追踪执行生命周期
│ ├── notify.ts # 多级通知(toast + 提示音 + 桌面通知)
│ ├── guarded.ts # 故障隔离包装(所有回调 try-catch)
│ ├── types.ts # TimerTask、RunRecord、TaskSchedule 类型
│ ├── schema.ts # 配置 schema(schemastery)
│ └── client/
│ ├── index.ts # Client 入口(设置 section 注册)
│ ├── TimerSettingsSection.tsx # 主设置 UI(列表 + 表单 + 历史)
│ ├── client-i18n.ts # 国际化(中/英)
│ ├── config-api.ts # Client 端 RPC 包装
│ ├── model-catalog.ts # 模型选择下拉 UI
│ └── chime.ts # WebAudio 双音提示音合成
├── lib/ # 构建产物
├── cordis.patch.yml # Bundle patch 声明
├── package.json
└── tsdown.config.ts
Host 端(src/)
| 模块 | 职责 |
|---|---|
index.ts |
插件启动:安装设置面板、启动 timer、配置变更时同步 |
remote.ts |
RPC API:list / create / update / delete / fireNow / runs |
timer-runtime.ts |
cron 解析、tick 轮询、agent followup 注入、生命周期追踪 |
lifecycle-tracker.ts |
监听 session/event,匹配注入的 messageId,更新 run 状态 |
notify.ts |
toast(React portal)+ 提示音(WebAudio)+ 桌面通知(Electron IPC) |
guarded.ts |
所有回调包在 try-catch 中;插件故障不拖垮宿主 |
schema.ts |
schemastery 配置校验 |
Client 端(src/client/)
| 模块 | 职责 |
|---|---|
index.ts |
通过 ctx.slots.inject 注册设置 section |
TimerSettingsSection.tsx |
React 组件:任务列表、创建/编辑表单、执行历史面板 |
client-i18n.ts |
中英文翻译 |
config-api.ts |
RPC 客户端包装 |
model-catalog.ts |
模型选择下拉 UI |
chime.ts |
WebAudio 双音提示音合成(零音频文件) |
持久化
| 数据 | 存储方式 | 说明 |
|---|---|---|
| 任务定义 | dsh-settings |
UI 可编辑,revision 防冲突 |
| 执行历史 | dsh-storage-domain |
结构化 KV,封顶 500 条 |
Tick 轮询策略
使用 setInterval tick 轮询(默认 15s)而非每个任务一个 setTimeout:
- 避开
setTimeout的2^31 - 1ms(~24.8 天)上限 - 重启恢复:从持久化任务重新计算
nextFireMap - 错过窗口:重启后首次 tick 立即触发,由 catch-up 策略处理
- 触发精度:受
tickSeconds限制(定时任务场景完全可接受)
已知问题与解决方案
重启后 Session ID 冲突
问题:桌面重启后,任务配置中持久化的 sessionId 可能与已存在的 agent session 冲突,导致 agents.create 报 "session already exists"。
根因:sessionId 被持久化到 settings 中;恢复时旧 ID 与 agent 在磁盘上的会话日志冲突。
我们的方案:sessionId 不持久化。每次触发时通过 agents.create 用新 UUID 创建专属会话。配置中的 sessionId 是临时的——只在 session 生命周期内追踪 agent handle。
Agent 被 dispose 后 handle 过期
问题:Agent 被外部 dispose(如用户关闭 session),但 runtime 仍持有过期 handle。
我们的方案:ensureAgent 检测 handle 是否过期(已 dispose 或不在 agents.list() 中)。若过期则 dispose handle 并轮换 sessionId 以创建新会话。
会话日志文件被删除后 ENOENT
问题:用户删除了会话日志文件,但 agent 仍在运行,无法写日志。
我们的方案:agent/error 监听器检测 ENOENT → dispose agent → 轮换 sessionId → 创建新会话。
归档会话
问题:dsh 通过标记 archivedSessionIds 归档会话,但不 dispose agent。agents.get() 仍返回 agent,followup 照常执行但用户 UI 看不见。
我们的方案:ensureAgent 中检查 workspaceRegistry.archivedSessionIds,若已归档则 dispose agent 并轮换 sessionId 创建新会话。
未挂载 preset 导致模型选择失效
问题:agents.create 不带 setup 回调不会挂载 standard preset,agent 无工具且 prompt assembly 无法解析 {{provider}} / {{model}} 变量。
我们的方案:所有 agents.create / agents.resume 调用均带 setup 回调,在 agent 发布前挂载 agentPresets('standard')并安装模型选择。
国际化
支持中文和英文。翻译文件在 src/client/client-i18n.ts。语言切换跟随 dsh 桌面端设置。
技术栈
- 语言:TypeScript
- 构建:tsdown (rolldown)
- 前端:React 18
- Cron 解析:
cron-parser(~30KB) - 人类可读 Cron:
cronstrue - 配置 Schema:
@deepseek-ai/schemastery - 设置持久化:
@deepseek-ai/dsh-settings - 存储:
@deepseek-ai/dsh-storage-domain
许可证
MIT