后台任务面板
上级:events
状态: 已在 v0.1.76 发布(2026-09-27)—— 设计与落地记录见 StandMeet 仓库的 docs/design/event-bus-outbox-webhooks.md。
队列、周期任务、事件流、投递日志都在 admin 里看得见、点得动。参考 River UI 的信息结构,但不引入它(它是独立服务 + 自己的认证);用 dispatcher ops 实现,后台和 MCP 同时可用。owner 也可以直接问自己的 AI“有没有卡住的任务?”。
面板结构
?job=<id>直接打开某个任务,webhook 投递日志就链到这里。- Webhooks 是集成组下单独的后台页(webhooks),不是这个面板的一个标签。
- 总览上的告警:
events_backlog(未扇出超过 1000 条,或最老的超过 5 分钟)、events_poisoned(有隔离的事件)、jobs_discarded(有 discarded 任务)。见 storage-bounds。
取代了进程内注册表
monitor 面板里原来的周期任务列表由进程内 JobRegistry 驱动,重启即丢。注册表和进程内调度器都已删除。周期任务跑在 River 上,instance.jobs(System 面板)改读 River 的持久记录。
ops(声明一次,投影到 admin 与 MCP)
这些 ops 放在 stats 域(internal/stats/ops/tasks.go、tasks_events.go)。后台经 /api/admin/tasks* 和 /api/admin/events* 访问。
- 读:
tasks.overview、tasks.list、tasks.get、tasks.periodic、events.list、events.get。 - 动作:
tasks.retry、tasks.cancel、tasks.run_periodic、events.requeue。 - 计划里任务的「丢弃」就是
cancel:River 要永久停掉一个任务只能取消它,再加一个动词做的是同一件事。 webhooks.deliveries是webhook.deliver任务的筛选视图。见 webhooks。tasks.get只给任务状态;属于某个域的结果形状由它自己的 op 返回(jobs.fetch_result,completion-hooks)。
这些 ops 走 Inspector 端口,面板不直接依赖 River(queue-behind-ports)。
权限:只在 owner 平面
这些 ops 只有 owner 读和 owner 动作,不进 API-key 面;任务参数和事件 payload 本来就是 thin 的,面板不显示语料正文(event-model)。
手动重试一条卡住的投递
重试策略与失败分类见 retry-has-one-owner。
验收
e2e tasks-panel 和 tasks-panel-more 走真实 UI,每条断言页面上看得见的内容:
- 总览数字与实际一致:造 3 个 retryable、1 个 discarded,总览显示 3 和 1,并显示最老待处理年龄。
- 任务列表按状态和 kind 筛选,结果只含所选。kind 筛选列出所有已声明的 kind,没跑过的也列。
- 任务详情显示每次尝试的错误和下次重试时间。
- 点「立即重试」→ 状态变 completed,且对应副作用真发生。
- 点「取消」→ 任务不再执行,副作用没有发生(用哨兵任务证明队列在跑)。
- 周期任务列表显示上次 / 下次运行;点「立即运行」后上次运行时间更新。
- 重启 backend 后面板仍显示重启前的周期任务运行记录。
- 事件流:改一篇笔记 → 出现对应事件,详情里列出它扇出到的订阅方及各自状态。
- webhook 投递日志可跳到任务详情;「一键重投」把该端点的 discarded 全部送达。
- 告警:积压超阈值时面板显示告警,排空后消失。
- 权限:用 API key(非 owner)调
tasks.*被拒。 - 完结 hook:批准申请后行状态从「发送中」变「已发送」;mail mock 先失败时显示「失败」,重试后变「已发送」;
jobs.fetch_new超过等待上限时返回回执,jobs.fetch_result返回同形状的结果(completion-hooks · async-response-contract)。
实现中顺手修的:切换行之后任务详情还作用在旧任务上;已修。