触发器让 MagicFrog 在没有人类回合的情况下行动:cron 触发器按计划触发一个动作,watcher 则循环运行一小段脚本,判断是否发生了需要你注意的事。两类共享同一个总览,也都拒绝猜测自己在哪里运行。
定时触发器(cron)
一条定时触发器携带自己的身份、一份计划和一个动作:
| 字段 | 含义 |
|---|---|
triggerId |
触发器的稳定身份 |
kind |
cron |
schedule |
非空的计划表达式 |
intervalMinutes |
整数;下限 30 分钟,低于即被拒绝 |
targetComputerId |
动作在其上运行的计算机——cron 必填 |
ownerTaskId |
可选:触发器所属的任务 |
action |
要运行什么(type 加可选参数) |
两条契约让计划值得信赖:
- **失败即关闭(fail-closed)的目标定向。**没有
targetComputerId的 cron 触发器在注册时即被拒绝,并在每次执行时复查。它绝不对本地机器运行,也绝不被静默改投他处。 - **不做补跑重放。**计划槽按网格对齐。若 daemon 宕机或 run 超时期间错过某个槽,触发器在下一个 tick 触发一次并重新对齐——错过的那次被向前跳过,绝不重放。
每次执行的结果都会记录:ok、error 或 rejected。
Watcher
watcher 是一段由你或 agent 编写的临时 bun 脚本,针对一个目标循环运行。脚本是配置;daemon 是解释器。
| 字段 | 含义 |
|---|---|
watcherId |
watcher 的稳定身份 |
scriptPath |
bun 脚本路径 |
targetKind |
task 或 agent |
targetId |
被观察的任务或 agent |
interval |
循环节拍,单位秒(30 分钟下限是 cron 的契约,不是 watch 循环的契约) |
ownerAgentId |
可选:拥有该观察的 agent |
status |
stopped、running 或 failed |
脚本契约
每轮迭代通过 stdin 收到自己的目标上下文,JSON 形态:
{
"watcherId": "wat_...",
"targetKind": "agent",
"targetId": "...",
"now": 1792000000000,
"artifactsDir": ".../watchers/wat_.../",
"events": [],
"eventCursor": 0
}
脚本在 stdout 上以一行 JSON finding 作答:
{ "matched": true, "summary": "Build failed twice in a row", "data": { "runId": "run_..." } }
什么算「matched」完全由脚本决定——执行器从不解释它。脚本以非零码退出记为一次失败的运行,watcher 继续存活;failed 状态保留给执行器层的启动失败,例如脚本不可读。
每个 watcher 有自己的产物目录,它既是子进程的工作目录,也以 MAGICFROG_WATCHER_ARTIFACT_DIR 暴露,所以脚本产出的文件绝不会在 watcher 之间相撞,也绝不会漏进工作区。
目标
agent——循环经 devices-ws 通道消费云端 agent 事件流:run.started、tool.called、tool.result、form.pending、form.answered与takeover.requested事件。事件流可按 cursor 续读:断连之后 watcher 从上次的 cursor 继续,而不是从头开始。task——循环收到注入的任务状态快照。若宿主没有可用的 task 探针,启动 watcher 显式失败——绝不出示伪造的状态。
watch 执行是本地优先:daemon 承载 watcher 循环,是它的 runtime owner。把 watcher 指向某个云端 agent 的事件,是订阅那个 agent 的 feed;watcher 本身并不迁移。
一个总览
GET /v1/triggers/overview 是全局聚合视图:一次账号范围的读取,列出每个任务的 cron 触发器与 watcher,各自带状态与按调度器裁定口径的下次到期时间。daemon 为本机暴露同一份单视图形态。两者都经同一个规范聚合契约渲染,所以控制面与机器本地状态不会漂移。