Chuyển đến nội dung chính

dsh-schedule-view

Đã xác minh

@lijian-ui/dsh-schedule-view · v0.1.1 · MIT · Giao diện web

Cron-based scheduled task plugin for DeepSeek Harness (dsh): pure UI management, zero LLM tools, cross-session timer, multi-level notifications.

Cài đặt

dsh plugin add @lijian-ui/dsh-schedule-view

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Thẻ

Readme

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。

使用方式

  1. 打开 dsh 桌面端
  2. 进入 设置定时任务
  3. 在任务列表中:
    • 点击 新建任务 创建定时任务
    • 点击滑块按钮启用/停用
    • 点击 编辑 修改调度规则、提示词或模型
    • 点击 立即执行 手动触发一次
    • 点击 删除 永久移除
    • 点击 执行历史 查看运行记录

任务字段

字段 必填 说明
任务名称 任务名
调度规则 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

  • 避开 setTimeout2^31 - 1 ms(~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)
  • 人类可读 Croncronstrue
  • 配置 Schema@deepseek-ai/schemastery
  • 设置持久化@deepseek-ai/dsh-settings
  • 存储@deepseek-ai/dsh-storage-domain

许可证

MIT

相关链接