Files
MyAiDesk/nomifun-tauri/docs/guides/scheduled-tasks.zh.md
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

222 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 定时任务 (Cron)
NomiFun 中的一个定时任务是一个在你选择的时间触发的循环 (或一次性)
任务,它会驱动一个 AI agent 去做某件事。你可以从定时任务页面
配置它、按需运行它、给它附加一个个性化的**技能**让 agent 在
该任务下始终以正确的方式行事,并且你可以使用一个内置的 cron
技能在聊天中让任何 agent 帮你管理任务。
> 找的是应该尽快运行的一次性异步工作,而不是按时钟来的?参见
> [AutoWork & Requirements](./autowork-requirements.md)。需要一个
> 实时 shell?参见 [应用内终端](./terminal.zh.md)。
![定时任务列表](../images/cron-01-list.png)
## 一个任务做什么
`nomifun-cron` 是一个后端调度器 + 执行器:
- **调度器**用一个 5 字段 (Unix) 或 6 字段 (秒前缀) 的 cron 表达式
为每个已启用的任务计算下一次触发时间 —— 两者都接受;
5 字段表达式会通过在前面加 `0` 作为秒被规范化为 6 字段。
调度也可以是一个绝对时间戳 (`At { at_ms }`) 或一个固定间隔
(`Every { every_ms }`)。
- 每个任务的时区 (例如 `Asia/Shanghai``America/Los_Angeles`)
会被尊重,所以 `0 9 * * MON` 表示**那个**时区的 09:00,而不是 UTC。
- **执行器**在定时器触发时驱动该任务的 agent。两种执行模式:
- **`new_conversation`** —— 每次触发开启一个新会话。该任务
携带 workspace、agent、model 和 prompt;执行器会创建会话、
广播一个 `cron_trigger` 工件 (这样聊天 UI 会显示
"本会话由一个定时任务发起"),然后发送 prompt。
- **`existing`** —— 复用拥有该任务的会话。每次触发把 prompt
作为一条新消息发送到同一个线程中。适合 "提醒我"、
"总结今天" 或任何延续性重要的任务。
- 一个**忙碌守卫**防止同一个会话被并发进入。如果上一次运行
在下一次触发到来时还在进行中,新的运行会被跳过 (记录为 `skipped`)。
- 一个**漏触发处理器**会在启动时和操作系统从睡眠中醒来后运行
(`/api/cron/internal/system-resume`)。它会遍历每个 `next_run`
在过去的已启用任务并发出一条系统消息,让你能看到漏掉一次
触发 (例如笔记本休眠时),然后为下一个 cron 节拍重新装定定时器。
- 每次触发会以一个状态被记录 —— `ok` / `error` / `skipped` /
`missed` —— 并且 (在适用时) 附带一个指向所产生会话的链接,
这样详情页可以向你展示运行历史。
## 创建一个任务
从侧边栏打开 **定时任务** (路由:`/scheduled`) 并按
**New task**。对话框涵盖四个区域。
### 频率
从一组小的预设中选择 —— `Manual` (无自动调度,仅通过 Run now 触发)、
`Hourly``Daily``Weekdays` (`MON-FRI`)、`Weekly`
`Custom`。预设会在 builder 中渲染出可编辑的 cron 表达式;选
**Custom** 直接键入。Builder 在你键入时进行校验。
Cron 语法速查 (5 字段 —— 秒字段会自动添加):
```
* * * * *
│ │ │ │ └─ 星期几 (06 或 SUNSATMON-FRI 可用)
│ │ │ └──── 月 (112 或 JANDEC)
│ │ └─────── 月中第几天 (1–31)
│ └────────── 小时 (0–23)
└───────────── 分钟 (0–59)
```
任务的时区在创建时设置 (默认是你浏览器的 IANA 时区) 并存储在
该行中;如果一个任务存储的时区因故无效,详情页会提供一键
修复到你的本地时区。
### Agent
选择每次触发运行的 agent。选择器中会显示三种类型:
- **CLI agent** —— `claude` / `codex` / `gemini` (后端在 `PATH`
上检测到的任何一个)。该任务记录后端标签并端到端使用 ACP。
- **Nomi (内置)** —— 使用 Nomi 自有引擎以及你选择的
provider/model。
- **Preset assistant** —— 预先配置的 agent 人格;该任务记录
assistant id。
**Advanced** 部分让你覆盖 workspace (agent 的工作目录)、model
以及任意的 `config_options` 键值对,它们会被转发给 agent 工厂。
Workspace 路径不能包含空白片段 —— 这一点在服务端被强制;
表单会把错误显式呈现出来。
### 执行模式
选择 `new_conversation``existing` (在 UI 中当你同时选择具体
是哪一个时被称为 "specified conversation")。详情页之后会向你
展示得到的会话。
### Prompt + 名称
**Prompt** 是每次发送给 agent 的内容。请把它写成一个**自包含的
指令** —— agent 看不到你原本 "我想要这个" 的框架,只看到这个
prompt。诸如下面这些模式:
- `Reply with a short weekly meeting reminder that includes the current date and time.`
- `Search for the latest AI news from this week and produce a concise bullet-point summary report.`
- `Run the weekly database health check and post the results back here.`
…比重述用户愿望要好。**Name** 只是一个标签。
![创建定时任务对话框](../images/cron-02-create-dialog.png)
## 运行、暂停、删除
列表视图 (`/scheduled`) 显示每一个任务、它的下一次触发,以及
一个启用开关。在详情页 (`/scheduled/:job_id`) 你可以:
- **Run now** —— 立即触发该任务,无视调度。忙碌守卫仍然适用。
- **Pause / Resume** —— 停止后续触发但不删除该行。
- **Edit** —— 与创建相同的对话框,处于编辑模式。
- **Delete** —— 删除该任务及其按任务生成的技能目录。此前运行创建的
会话会保留在会话列表中,可按需单独删除。
详情页还会列出本任务创建的会话,按活跃度排序 —— 当任务
`new_conversation` 模式运行并为每次触发各分出一个线程时
非常有用。
![定时任务详情](../images/cron-03-detail.png)
## 保持唤醒
只有当宿主进程在运行时,cron 任务才会触发。列表页有一个
**NomiFun 运行时保持系统唤醒**开关,它会请求 OS 抑制睡眠
(Windows`SetThreadExecutionState`macOS`caffeinate`
Linux 上若可用为 `systemd-inhibit`),这样你在笔记本上设置的
任务不会在合上盖子那一刻悄悄漏触发。
如果一次触发还是因为系统进入睡眠 (或 NomiFun 没在运行) 而漏掉,
下一次启动/唤醒时的漏触发处理器会记录一次 `missed` 运行并
向受影响的会话中投送一条系统消息,然后为下一次正常触发
重新装定定时器。
## 附加到任务的技能
一个**技能**是一个 `SKILL.md` 文件,agent 会在加入会话时读取它
—— 与 NomiFun 其他地方使用的相同机制,但作用域是按任务的。
你可以在详情页编写/编辑该技能;在幕后该文件会被写入数据
目录下的 `cron/skills/cron-<job_id>/SKILL.md`,执行器会在每次
触发时把它注入到 agent 的会话中。
用例:
- 任务输出的一致**人格** (风格、语气、格式)。
- **工具/MCP** 偏好 (启用哪些服务器、忽略哪些)。
- 工作区特定的约定 (commit 信息风格、目录布局、部署细节)。
任务有自己的技能目录 (以 job id 命名,前缀 `cron-`),所以共享同一
workspace 的两个任务可以承载不同的行为而不冲突。删除任务会
移除其技能目录。
还有一个自动的**技能建议**检测器,它会在运行期间观察 agent 的
输出;当它产出一个干净的候选技能时 (符合预期格式且不只是占位
模板),检测器会在会话中创建一个 `skill_suggest` 工件,让你
可以审查并一键将其保存为该任务的技能。
## 在聊天中管理任务 —— 内置的 `cron` 技能
NomiFun 附带一个名为 `cron` 的内置自动注入技能,任何 agent 都可以
在你让它"设置一个提醒"、"每周一安排 X" 等时加载它。
然后会话中间件会观察 agent 的回复中以下的指令块,并通过 cron
服务运行它们:
| 指令 | 含义 |
| --------------------- | ------------------------------------------------------- |
| `[CRON_LIST]` | 列出当前会话作用域内的 cron 任务。 |
| `[CRON_CREATE]…[/CRON_CREATE]` | 创建一个任务 (字段:`name``schedule``schedule_description``message`)。 |
| `[CRON_UPDATE: <id>]…[/CRON_UPDATE]` | 原地更新一个已有任务。 |
| `[CRON_DELETE: <id>]` | 按 id 删除一个任务。 |
中间件会从用户看到的内容中**剥离**这些块,并把系统响应
(`Created cron job 'X'``No scheduled tasks` 等) 投回到会话中。
所以在聊天里看起来像是正常的来回;幕后是 agent 发出了一个
指令,平台执行了它。
该技能在设计上被限制为**每个会话一个任务** —— 这让循环保持
简单 ("查询,然后行动") 并避免了你重新询问时重复任务堆积。
要一次管理多个任务,请直接使用定时任务页面。
## 路由 & API
| 内容 | 位置 |
| ------------------------------- | ----------------------------------------------------------------- |
| 列表页面 | `/scheduled` |
| 详情页面 | `/scheduled/:job_id` |
| 列出 / 创建任务 | `GET /api/cron/jobs``POST /api/cron/jobs` |
| 获取 / 更新 / 删除 | `GET|PUT|DELETE /api/cron/jobs/:id` |
| 立即运行 | `POST /api/cron/jobs/:id/run` |
| 列出某任务的会话 | `GET /api/cron/jobs/:id/conversations` |
| 每任务技能 | `GET|POST|DELETE /api/cron/jobs/:id/skill` |
| 系统恢复 (内部) | `POST /api/cron/internal/system-resume` (需要内部 header) |
UI 订阅的实时事件:`cron.job-created``cron.job-updated`
`cron.job-removed``cron.job-executed`。漏触发会作为一次
`cron.job-executed` 事件上报,payload 中的状态是 `missed`
## 故障排查
- **任务没有按时触发。** 那时宿主在运行且处于唤醒状态吗?
如果你合上了笔记本或关闭了应用,请查看唤醒后的下一条会话
条目 —— 漏触发处理器会投送一条 `missed` 通知并重新装定
定时器。
- **我的 cron 表达式被拒绝了。** 5 字段 (`m h dom mon dow`)
和 6 字段 (`s m h dom mon dow`) 形式都是合法的。在本地用
[crontab.guru](https://crontab.guru/) 或对话框中的 builder
校验。
- **任务运行了但 agent 做错了事。** 重新阅读 prompt,假设你没有
其他上下文。它必须告诉 agent 准确要产出什么。然后考虑附加
一个技能以锁定行为。
- **两次定时触发碰撞了。** 忙碌守卫会在 `existing` 模式下跳过
重叠的运行 (该运行被记录为 `skipped`)。如果你预期触发会
长时间运行,请把任务切换到 `new_conversation` 模式,让每次
触发各得一个线程。
- **聊天中的 `cron` 指令什么也没做。** 如果 cron 服务没有接入
(例如某些测试 harness),中间件会 no-op;在正常 app build 中
它总是接入的。如果指令格式错误 (缺失闭合标签、缺失
`schedule`),它会被静默丢弃 —— 用更干净的输入重新提示
agent。