work_manager 定时任务
work_manager 定时任务
work_manager 用来给当前项目里的脚本文件添加、查询、修改和删除定时任务。
它不是网页里那种 setTimeout(...),也不是 threads 里的线程内定时器,而是会真正落到 ScriptX 的任务调度系统里,按日期 / 时间 / 周几去执行指定脚本。
先记住 9 条:
- 模块名是
work_manager,别名是$work_manager。 - 这套 API 只在项目脚本的无宿主运行路径里注入,不是任何场景都存在。
- 当前
hook.js不会注入这套模块,所以不要在 Hook 脚本里假设它一定有。 - 它管理的是当前项目自己的任务,不会顺手把别的项目任务也带上。
task.path可以写绝对路径,也可以写当前项目内相对路径。- 相对路径最终必须落在当前项目目录里,不能越界到项目外。
- 目标脚本文件必须真实存在,而且必须是
.js文件。 repeatMode当前只有ONCE、DAILY、WEEKLY三种值。weeklyDays用1 ~ 7表示周一到周日,不是 JavaScript 里常见的0 ~ 6。
先分清它和普通定时器的区别
| 能力 | 更适合什么 |
|---|---|
setTimeout(...) / setInterval(...) | 当前脚本线程里的短期调度 |
thread.setTimeout(...) | 把任务挂到某个明确线程 |
work_manager | 脚本级、持久化、按时间表执行的项目任务 |
如果你要的是:
- 3 秒后在当前脚本里跑一个函数
- 或者每 1 秒回调一次
那就去看 threads 线程与同步 和 global-functions 全局函数。
如果你要的是:
- 每天 08:30 跑一次
- 某天某时执行一次指定脚本
- 每周一、三、五定时跑项目里的某个
.js
那就是这一页。
任务对象字段先看懂
addTask(...) / updateTask(...) 这套最核心的就是任务对象本身。
写入时常用字段
| 字段 | 类型 | 必填 | 可填值 | 说明 |
|---|---|---|---|---|
id | string | 是 | 当前项目内唯一任务名 | 任务主键 |
path | string | 是 | 绝对路径,或项目内相对路径 | 要执行的脚本文件 |
name | string | 否 | 任意非空描述名 | 不写时会退回脚本文件名或 id |
enabled | boolean | 否 | true / false | 是否启用,默认 true |
repeatMode | string | 否 | ONCE / DAILY / WEEKLY | 重复模式,默认 DAILY |
onceDate | string | 仅 ONCE 必填 | yyyy-MM-dd | 一次性任务触发日期 |
triggerTime | string | 是 | HH:mm 或 HH:mm:ss | 每次触发的时间 |
weeklyDays | number[] | 仅 WEEKLY 必填 | 1 ~ 7 的整数数组 | 周任务触发日,1=周一,7=周日 |
wakeScreen | boolean | 否 | true / false | 运行前是否尝试唤醒屏幕 |
notifyOnCompletion | boolean | 否 | true / false | 执行结束后是否发完成通知 |
查询 / 返回结果里的字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 你传入的公开任务 id |
name | string | 任务显示名 |
path | string | 脚本绝对路径 |
enabled | boolean | 当前是否启用 |
repeatMode | string | ONCE / DAILY / WEEKLY |
onceDate | string | 一次性任务日期;不是 ONCE 时通常为空字符串 |
triggerTime | string | 标准化后的触发时间,格式会变成 HH:mm:ss |
weeklyDays | number[] | 周任务触发日列表 |
wakeScreen | boolean | 是否唤屏 |
notifyOnCompletion | boolean | 是否完成通知 |
nextTriggerAt | number | 下次触发时间戳,单位毫秒;没有下次任务时可能是 0 |
work_manager.addTask(task)
新增一个任务;如果同一个 id 已经存在,本质上会按当前 id 覆盖更新。
返回值
- 返回任务对象
默认值、自动回退和布尔转换规则
| 字段 | 当前规则 |
|---|---|
id | 会先 trim();去掉空格后如果还是空字符串,就直接报错 |
path | 会先 trim();不能为空;目标文件必须真实存在,而且必须是 .js |
name | 不写或写空时,会先回退到“脚本文件名去掉 .js 后的名字”;如果文件名也拿不到,再回退到 id |
enabled | 默认 true |
repeatMode | 默认 DAILY |
wakeScreen | 默认 false |
notifyOnCompletion | 默认 false |
布尔字段不是“只认布尔值”这么简单,源码当前用的是同一套解析规则:
| 传入值 | 解析结果 |
|---|---|
true / false | 原样保留 |
数字 0 | false |
| 其他非零数字 | true |
字符串 "true"(忽略大小写) | true |
| 其他字符串 | false |
| 不写这个字段 | 使用该字段自己的默认值 |
所以这些写法在当前实现里都成立:
work_manager.addTask({
id: "night-clean",
path: "tasks/night-clean.js",
triggerTime: "23:30",
enabled: 1,
wakeScreen: "true",
notifyOnCompletion: "false"
});
上面最终会等价成:
{
enabled: true,
wakeScreen: true,
notifyOnCompletion: false
}
路径规则一定要看清
相对路径
相对路径会按当前项目根目录去找:
work_manager.addTask({
id: "daily-report",
path: "tasks/daily-report.js",
repeatMode: "DAILY",
triggerTime: "08:30"
});
这时它最终会解析到:
Project/<当前项目名>/tasks/daily-report.js
绝对路径
也可以直接传绝对路径:
work_manager.addTask({
id: "night-clean",
path: "C:/Users/Administrator/Desktop/JSXHookDoc/demo/night-clean.js",
repeatMode: "DAILY",
triggerTime: "23:30"
});
不能越界
如果你传的是相对路径,当前实现会做 canonical 校验。
也就是说下面这种想法是行不通的:
path: "../other-project/run.js"
因为它会被拦下来,要求脚本必须仍然在当前项目目录里。
repeatMode 可填值
这三个值当前会先转成大写再解析,所以:
ONCE/once/Once都能识别DAILY/daily都能识别WEEKLY/weekly都能识别
ONCE
只执行一次,必须同时给出 onceDate。
work_manager.addTask({
id: "release-once",
path: "tasks/release-once.js",
repeatMode: "ONCE",
onceDate: "2026-08-10",
triggerTime: "09:30"
});
DAILY
每天固定时间执行一次。
work_manager.addTask({
id: "daily-sync",
path: "tasks/daily-sync.js",
repeatMode: "DAILY",
triggerTime: "08:00"
});
WEEKLY
每周指定几天执行,必须给 weeklyDays。
work_manager.addTask({
id: "weekly-clean",
path: "tasks/weekly-clean.js",
repeatMode: "WEEKLY",
triggerTime: "22:15",
weeklyDays: [1, 3, 5]
});
这里 [1, 3, 5] 表示:
1:周一3:周三5:周五
triggerTime 可填格式
当前支持两种:
HH:mmHH:mm:ss
例如:
"08:30"
"08:30:15"
最终都会被标准化成 HH:mm:ss。
onceDate 和 weeklyDays 也有硬校验
| 字段 | 什么时候必填 | 可填值 | 当前行为 |
|---|---|---|---|
onceDate | repeatMode = ONCE | yyyy-MM-dd | 会先校验日期格式,再标准化保存 |
weeklyDays | repeatMode = WEEKLY | 1 .. 7 的数字数组 | 会先去重、排序,再保存 |
例如:
work_manager.addTask({
id: "weekly-report",
path: "tasks/weekly-report.js",
repeatMode: "weekly",
triggerTime: "08:30",
weeklyDays: [5, 1, 5, 3]
});
最终保存下来的 weeklyDays 会变成:
[1, 3, 5]
完整例子:晨间任务
const task = work_manager.addTask({
id: "morning-check",
name: "早晨巡检",
path: "tasks/morning-check.js",
enabled: true,
repeatMode: "DAILY",
triggerTime: "07:30",
wakeScreen: true,
notifyOnCompletion: true
});
log(JSON.stringify(task, null, 2));
work_manager.removeTask(id)
按 id 删除当前项目里的任务。
返回值
booleantrue:找到了并删除了false:当前项目下没有这个任务
例子
const removed = work_manager.removeTask("morning-check");
log("removed = " + removed);
work_manager.getTask(id)
按 id 读取单个任务。
返回值
object- 或
null
例子
const task = work_manager.getTask("weekly-clean");
if (task) {
log(JSON.stringify(task, null, 2));
}
work_manager.queryTasks()
读取当前项目名空间下的全部任务。
返回值
object[]
例子
const tasks = work_manager.queryTasks();
tasks.forEach(function (task) {
log(task.id + " -> " + task.triggerTime);
});
一个重要边界
这不是“全应用所有任务总表”。
当前实现会按内部前缀 work_manager:<projectName>: 过滤,只返回当前项目拥有的任务。
work_manager.updateTask(task)
更新已有任务。
支持两种写法
写法 1:整个对象里自带 id
work_manager.updateTask({
id: "daily-sync",
triggerTime: "09:00",
notifyOnCompletion: true
});
写法 2:显式传 id + patch
work_manager.updateTask("daily-sync", {
triggerTime: "09:00",
notifyOnCompletion: true
});
返回值
- 更新成功:返回新的任务对象
- 任务不存在:返回
null
更新规则
- 只改你 patch 里明确传了的字段
- 没传的字段保留原值
task.id不能借着更新去改名
还有一个很容易忽略的细节:
addTask(...)里name留空时会自动回退到文件名 /idupdateTask(...)如果你显式传了name: "",当前实现会真的把任务名改成空字符串,而不是再次帮你回退
所以更新时如果你只是“不想改名字”,最稳的是不要传 name 字段。
改 repeatMode 时要注意
改成 ONCE
那你必须让 onceDate 也同时有效:
work_manager.updateTask("daily-sync", {
repeatMode: "ONCE",
onceDate: "2026-08-12",
triggerTime: "10:00"
});
改成 WEEKLY
那你必须让 weeklyDays 也同时有效:
work_manager.updateTask("daily-sync", {
repeatMode: "WEEKLY",
weeklyDays: [2, 4, 6],
triggerTime: "21:00"
});
如果你只改了 repeatMode,却没给对应必需字段,当前实现会直接报错。
work_manager.updateTask(id, patch)
这就是同一个更新入口的“拆参”写法:把任务 id 单独传,第 2 个参数只放这次要改的字段。
work_manager.updateTask("daily-sync", {
triggerTime: "09:30",
notifyOnCompletion: false
});
它最适合这几种情况:
- 你已经明确知道任务 id。
- 你只想改一两个字段,不想回填整对象。
- 你在按钮点击、菜单操作这类局部逻辑里做快速更新。
work_manager.enableTask(id)
启用一个任务。
返回值
- 任务存在:返回更新后的任务对象
- 任务不存在:返回
null
例子
const task = work_manager.enableTask("weekly-clean");
log(JSON.stringify(task, null, 2));
work_manager.disableTask(id)
禁用一个任务。
返回值
- 任务存在:返回更新后的任务对象
- 任务不存在:返回
null
例子
const task = work_manager.disableTask("weekly-clean");
log(JSON.stringify(task, null, 2));
work_manager.clearTasks()
清空当前项目名下的所有任务。
返回值
number- 返回本次一共删除了多少条
例子
const count = work_manager.clearTasks();
log("cleared = " + count);
作用范围
它只会删掉:
- 当前项目前缀下的任务
不会去碰其他项目的任务。
work_manager.api
指回模块对象本身。
例子
log(work_manager.api === work_manager); // true
常见错误别踩
1. 在 Hook 脚本里直接用
这套模块当前注入条件很明确:
- 要是项目脚本
- 要走无宿主运行路径
- 还不能是
hook.js
所以如果你在 Hook 场景里直接写:
work_manager.addTask(...);
很可能模块本身就不存在。
2. 把 weeklyDays 写成 0 ~ 6
当前实现要求的是:
1 = 周一7 = 周日
不是 JS Date.getDay() 那种周日 0 开头。
3. task.path 指向了不存在文件
当前实现会在写入时就检查:
- 文件必须真实存在
- 必须是
.js
所以不要先记任务、后补脚本。
4. 以为 name 不写就会失败
不会。
如果你不写 name,当前实现会按:
- 脚本文件名(不带扩展名)
- 再退到
id
自动补一个任务名。
一个完整项目例子
假设你的项目里有:
tasks/
morning-check.js
weekly-clean.js
那你可以这样初始化:
work_manager.addTask({
id: "morning-check",
path: "tasks/morning-check.js",
repeatMode: "DAILY",
triggerTime: "07:30",
wakeScreen: true
});
work_manager.addTask({
id: "weekly-clean",
path: "tasks/weekly-clean.js",
repeatMode: "WEEKLY",
triggerTime: "22:00",
weeklyDays: [6],
notifyOnCompletion: true
});
log(JSON.stringify(work_manager.queryTasks(), null, 2));
这时候 [6] 表示周六执行。
