定时任务与通知
提醒、待办、上课提醒和定时检查都由同一个定时任务引擎驱动。每个任务由一个触发器(单次,或有限次的重复)和一个动作(通知、检查或助手)组成,准时到分钟触发。
PKU harness 里所有和时间有关的功能都由同一个定时任务引擎(scheduler)处理。引擎里的每一项叫作一个任务(job)。例如,一条提醒、某个待办的提醒、上课提醒,以及「每天早上查一下我的校园卡余额」,都是任务。
你可以在「日程」标签页里创建任务,也可以直接让助手创建。插件通过 services.scheduler 创建任务。
每个任务由两部分组成:触发器(trigger)决定它什么时候运行,动作(action)决定它运行时做什么。
任务的结构
type Job = {
id: string;
title: string;
trigger: Trigger; // 何时触发
action: Action; // 做什么
enabled: boolean;
source: 'user' | 'agent' | 'plugin';
runs: number; // 已触发(或已错过)的次数
lastRunAt: number | null;
nextRunAt: number | null; // 系列结束后为 null
// check / agent 任务还会记录 lastValue、lastError、history(最近 20 次)、failures …
meta?: Record<string, unknown>; // 指回所属对象,例如 { todoId }
createdAt: number;
updatedAt: number;
};触发器
触发器有两种,所有时间都按手机的本地时间计算。
单次触发器 { type: 'at' } 只有一个字段 at,是一个毫秒时间戳。它适用于「两天后」「明天 8:00」这样的提醒。
重复触发器 { type: 'every' } 适用于「每天早上 8 点」「每 3 天一次,共 16 次后停止」「每周一、三、五 07:30,直到学期结束」这样的提醒。它有以下字段。
| 字段 | 含义 |
|---|---|
unit | 重复的单位:minute、hour、day 或 week |
interval | 每隔几个单位重复一次,取值 1–1000 |
times? | 一天中的时刻,格式为 "HH:MM",用于 day 和 week |
weekdays? | 星期几,0 表示周日,6 表示周六,用于 week |
startAt | 开始时间 |
count? | 最多触发几次 |
until? | 截止日期(含当天) |
重复触发器的计算方法如下。一个重复系列由若干个周期组成,每个周期是一个分钟或小时步长、一天,或者从周一开始的一周。每个周期里有若干个时间点,由时刻和星期组合而成。早于 startAt 的时间点会被跳过,剩下的依次编号为 0、1、2……,最后由 count、until 或两者共同截断。
助手和表单使用一种更容易书写的输入格式 ScheduleInput,时间写成本地时间字符串 "YYYY-MM-DD HH:mm"。例如,「每 3 天一次,09:00,共 16 次」写作:
{ "type": "repeat", "unit": "day", "interval": 3, "times": ["09:00"], "count": 16 }toTrigger(input) 把它转换成触发器。summarize(trigger) 返回 { schedule, next, total, finished },例如 "每 3 天 09:00 · 共 16 次" 以及接下来的三个触发时间。助手可以在保存之前把这些时间告诉你,请你确认。
动作
动作有三种。
| 动作 | 字段 |
|---|---|
notify | title、body?、url? |
check | tool、args?、condition、title、body?、url? |
agent | prompt(4–1000 个字符) |
notify 发送一条通知。它会提前注册为精确通知,触发时不运行任何代码。url 是点击通知后打开的 App 内路由。
check 是定时检查。它调用一个只读工具(形如 <plugin>.<tool>),读取结果中 condition.path 处的值,只有条件成立时才发送通知。通知文案里的 {value} 会被替换成读到的值。
agent 让助手定时运行一次。每次运行都新开一个只读的助手会话,不带任何聊天记录。如果助手回复 NO_REPLY,就表示这次不需要打扰你;否则,它的简短回答就是通知的内容。
check 的条件写作 { path, op, value? }。op 取以下值之一:<、<=、>、>=、==、!=、changed、always。
下面的检查在校园卡余额低于 100 元时提醒你充值:
{
"type": "check",
"tool": "wallet.cardBalance",
"condition": { "path": "balance", "op": "<", "value": 100 },
"title": "校园卡余额只剩 ¥{value},记得充值"
}在 Android 上准时送达
notify 任务会提前注册为精确闹钟。注册的范围是未来 14 天,每个任务最多 64 个,总共最多 400 个。App 启动时、任务每次变更时、App 回到前台时,以及后台任务运行时,都会重新注册。
check 和 agent 任务在 JavaScript 里运行。App 在前台时,由计时器准时到分钟触发它们。App 在后台或已被系统关闭时,由精确闹钟唤醒一个后台任务(headless task)。这个后台任务会执行所有到期的任务,注册下一个闹钟,并移除占位通知。
准时送达需要两项权限:通知权限,以及「闹钟和提醒」这项特殊权限。Android 14 及以上版本默认不授予后者。关闭电池优化只是建议,不是必需的。
插件可以用以下函数检查和修复这些设置。
services.notify.readiness()报告这三项的状态。deliveryBlocker(readiness)指出第一个阻碍准时送达的项目。services.notify.fix(kind)打开对应的系统弹窗或设置页。fix必须在用户点击时调用。
无人值守时的限制
定时检查运行时,没有人在旁边看着。所以它的权限受到限制,每次运行都有记录,失败时会明确提醒你。
| 规则 | 取值 |
|---|---|
| 可调用的工具 | 只能是 read 工具 |
| 启用中的任务 | 最多 50 个(MAX_ACTIVE_JOBS) |
| 检查频率 | 最多每 15 分钟一次(MIN_CHECK_GAP_MS) |
| 助手任务的数量 | 最多 10 个(MAX_AGENT_JOBS) |
| 助手任务的频率 | 最多每小时一次(MIN_AGENT_GAP_MS) |
| 助手任务每次的步数 | 最多 8 步(AGENT_MAX_STEPS) |
| 迟到的运行 | 迟到超过 2 小时(MAX_LATE_MS)记为错过 |
| 运行日志 | 保留最近 20 次(HISTORY_LIMIT) |
| 连续失败后提醒 | 2 次(FAIL_ALERT_AFTER) |
| 连续失败后停用 | 10 次(DISABLE_AFTER) |
对这些规则的补充说明如下。
- 工具的效果在运行时会重新检查。如果授权方式为
ask,这次调用视为被拒绝。 - 不允许创建重复的检查,也就是工具、参数和条件都相同的检查。
- 迟到的运行记为错过,是为了避免几个小时后才弹出一条早已过时的提醒。
- 运行日志记录计划时间和实际时间、结果、读到的值和错误。
- 连续失败 2 次时,App 会提醒你一次,6 小时后如果仍然失败,会再提醒一次。连续失败 10 次时,App 会停用这个任务并告诉你。
写给插件作者
- 让工具适合被检查。 这样的工具效果为
read,响应快,并且有一个稳定的数字或布尔字段可以比较,例如balance、unread或available。请把这些工具列在dataSources里,助手才能找到它们。 - 只在响应用户操作时创建任务, 写法是
services.scheduler.create({ …, source: 'plugin' })。请保存好任务 id(放在meta或你的kv里),之后才能更新或删除这个任务。 - SDK 导出了纯计算的引擎函数, 例如
occurrencesAfter、nextOccurrence、describeTrigger和evaluate。详见 SDK → scheduler。