跳到内容
MagicFrog

触发器

定时 run 与事件 watcher——两者都绑定显式目标,并在同一个总览中可见。

触发器让 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 为本机暴露同一份单视图形态。两者都经同一个规范聚合契约渲染,所以控制面与机器本地状态不会漂移。