mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-30 10:59:31 +02:00
Compare commits
2 Commits
agent/lamb
...
agent/j/0c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e3f3809b | ||
|
|
00226afa0f |
@@ -99,6 +99,126 @@ nothing matches, the event is `webhook.received`.
|
||||
When configuring GitHub or similar sources, set the content type to
|
||||
`application/json` — form-encoded webhook payloads are not accepted.
|
||||
|
||||
### Event filters
|
||||
|
||||
A new webhook trigger fires on every inbound POST, which is fine for a
|
||||
single-purpose URL but noisy for sources that fan out many event types
|
||||
(GitHub being the obvious one — a single repo webhook can deliver
|
||||
`push`, `pull_request`, `workflow_run`, `check_suite`, and more). The
|
||||
**Event filters** section on a webhook trigger lets you restrict which
|
||||
events actually dispatch a run; everything else is recorded in delivery
|
||||
history with `status = ignored` and `reason = event_filtered`, and no
|
||||
run or issue is created.
|
||||
|
||||
Each row is one rule: an **event name** plus an optional
|
||||
comma-separated **actions** list. Multica allows a webhook if **any**
|
||||
row matches; leave the section empty to accept everything (the
|
||||
pre-filter behavior).
|
||||
|
||||
Examples:
|
||||
|
||||
| Event name | Actions | Matches |
|
||||
| -------------- | ------------------- | ------------------------------------------------------------------------ |
|
||||
| `workflow_run` | `completed, failed` | `workflow_run` events with `action: completed` or `action: failed` only |
|
||||
| `workflow_run` | _(empty)_ | every `workflow_run` event, regardless of action |
|
||||
| `push` | _(empty)_ | every `push` event |
|
||||
|
||||
#### Where the event name and action come from
|
||||
|
||||
Multica derives the `event` name and `action` from the inbound request
|
||||
in this order — **the first match wins**.
|
||||
|
||||
**1. Body envelope.** If the body is a JSON object with a string
|
||||
`event` field, that value is the event name directly. An optional
|
||||
`eventPayload` object then supplies action candidates from its
|
||||
`action` / `state` / `conclusion` / `status` fields.
|
||||
|
||||
```bash
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"event":"trigger","eventPayload":{"action":"true"}}'
|
||||
# inferred: event = trigger, action candidate = true
|
||||
```
|
||||
|
||||
**2. Headers.** When no body envelope is present, Multica reads the
|
||||
following well-known provider headers:
|
||||
|
||||
- `X-GitHub-Event: <event>` — combined with the top-level body
|
||||
`action` field (when present) to form `github.<event>.<action>`.
|
||||
- `X-Gitlab-Event: <event>` — becomes `gitlab.<event>`.
|
||||
- `X-Event-Type: <event>` — passed through verbatim.
|
||||
|
||||
```bash
|
||||
# GitHub-style: header gives the event name, body gives the action.
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-GitHub-Event: workflow_run' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"completed"}'
|
||||
# inferred: event = github.workflow_run.completed
|
||||
# → matches a filter row of workflow_run / completed
|
||||
|
||||
# Generic event-type header — no body fields needed.
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-Event-Type: trigger.true' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{}'
|
||||
# inferred: event = trigger.true → matches trigger / true
|
||||
```
|
||||
|
||||
**3. Body fallback.** If neither a body envelope nor a known header is
|
||||
present, Multica falls back to top-level body string fields in this
|
||||
order: `event` → `type` → `action`.
|
||||
|
||||
```bash
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"type":"trigger","action":"true"}'
|
||||
# inferred: event = trigger (from `type`), action candidate = true
|
||||
```
|
||||
|
||||
**4. Default.** If nothing above matches, the event is
|
||||
`webhook.received` and there are no action candidates.
|
||||
|
||||
**Action candidates, in full.** Once the event is determined, Multica
|
||||
considers every value below as a possible action match:
|
||||
|
||||
- The event-name suffix, when the event has the form
|
||||
`provider.event.<action>` (e.g. `github.workflow_run.completed` →
|
||||
`completed`).
|
||||
- The body fields `action`, `state`, `conclusion`, and `status` —
|
||||
**only when they are JSON strings**. A boolean (`{"action": true}`)
|
||||
or a number does not qualify, so a filter expecting
|
||||
`event=trigger, action=true` will never match a body of
|
||||
`{"trigger": true}` because `true` is a bool, not a string.
|
||||
|
||||
**Common gotcha.** A filter row like `Event name: trigger` /
|
||||
`Actions: true` does **not** mean "fire when the body has
|
||||
`trigger: true`" — Event filters match the *inferred event and
|
||||
action*, not arbitrary body fields. Send `trigger.true` via
|
||||
`X-Event-Type` (or use the body envelope shown above) to hit it.
|
||||
Surrounding whitespace in saved filter rows (`" workflow_run "`) is
|
||||
stored verbatim and will never match — trim before saving.
|
||||
|
||||
#### Quick test
|
||||
|
||||
Once a filter is configured, you can confirm both branches with `curl`:
|
||||
|
||||
```bash
|
||||
# Allowed — header drives event=workflow_run, body drives action=completed
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-GitHub-Event: workflow_run' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"completed"}'
|
||||
# → 200 {"status":"accepted", ...}
|
||||
|
||||
# Filtered — same event, action not in allowlist
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-GitHub-Event: workflow_run' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"in_progress"}'
|
||||
# → 200 {"status":"ignored","reason":"event_filtered"}
|
||||
```
|
||||
|
||||
### URL is a bearer secret
|
||||
|
||||
The generated URL **is** the credential. Anyone with it can fire the
|
||||
|
||||
@@ -98,6 +98,116 @@ curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
配置 GitHub 之类的来源时,请把 content type 设为 `application/json`——
|
||||
表单编码的 webhook payload 在 v1 里不接受。
|
||||
|
||||
### 事件过滤
|
||||
|
||||
新建的 webhook 触发器对每一条入站 POST 都会触发,这在单一用途的 URL
|
||||
上没问题,但对那种会扇出很多事件类型的来源(典型就是 GitHub——
|
||||
一个仓库 webhook 就会同时下发 `push`、`pull_request`、`workflow_run`、
|
||||
`check_suite` 等等)就会很吵。webhook 触发器上的**事件过滤**区块用来
|
||||
限制哪些事件真正派发一次 run;其它的只记录到投递历史里,
|
||||
`status = ignored`、`reason = event_filtered`,不会建任何 issue 或 run。
|
||||
|
||||
每一行是一条规则:一个**事件名**加可选的、逗号分隔的 **action** 列表。
|
||||
**任意一行命中**即放行;区块留空则接受所有事件(即过滤前的行为)。
|
||||
|
||||
例子:
|
||||
|
||||
| 事件名 | Actions | 命中范围 |
|
||||
| ---------------- | ------------------- | ------------------------------------------------------------------------ |
|
||||
| `workflow_run` | `completed, failed` | 只有 `action` 为 `completed` 或 `failed` 的 `workflow_run` 事件 |
|
||||
| `workflow_run` | _(留空)_ | 所有 `workflow_run` 事件,不限 action |
|
||||
| `push` | _(留空)_ | 所有 `push` 事件 |
|
||||
|
||||
#### 事件名和 action 从哪来
|
||||
|
||||
Multica 按下面的顺序从入站请求里推断 `event` 和 `action`,**先命中先用**。
|
||||
|
||||
**1. Body envelope。** 如果 body 是一个 JSON 对象、且带字符串字段
|
||||
`event`,就直接用它作为事件名。可选的 `eventPayload` 对象再
|
||||
从自己的 `action` / `state` / `conclusion` / `status` 字段里提供
|
||||
action 候选。
|
||||
|
||||
```bash
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"event":"trigger","eventPayload":{"action":"true"}}'
|
||||
# 推断结果:event = trigger,action 候选 = true
|
||||
```
|
||||
|
||||
**2. 请求头。** 没有 body envelope 时按以下头部识别:
|
||||
|
||||
- `X-GitHub-Event: <event>` —— 结合 body 顶层的 `action` 字段
|
||||
(如果有),拼成 `github.<event>.<action>`。
|
||||
- `X-Gitlab-Event: <event>` —— 拼成 `gitlab.<event>`。
|
||||
- `X-Event-Type: <event>` —— 原样使用。
|
||||
|
||||
```bash
|
||||
# GitHub 风格:事件名来自 header,action 来自 body。
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-GitHub-Event: workflow_run' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"completed"}'
|
||||
# 推断结果:event = github.workflow_run.completed
|
||||
# → 命中 workflow_run / completed 的过滤规则
|
||||
|
||||
# 通用 event-type 头部 —— 不需要任何 body 字段。
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-Event-Type: trigger.true' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{}'
|
||||
# 推断结果:event = trigger.true → 命中 trigger / true
|
||||
```
|
||||
|
||||
**3. Body 兜底。** Body envelope 和上面的 header 都没有时,
|
||||
从 body 顶层字符串字段里依次找:`event` → `type` → `action`。
|
||||
|
||||
```bash
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"type":"trigger","action":"true"}'
|
||||
# 推断结果:event = trigger(取自 `type`),action 候选 = true
|
||||
```
|
||||
|
||||
**4. 默认值。** 以上都没命中时,事件名取 `webhook.received`,
|
||||
没有 action 候选。
|
||||
|
||||
**action 候选的完整清单。** 事件名确定后,下面这些值都会被列为
|
||||
可能的 action:
|
||||
|
||||
- 事件名后缀,当事件形如 `provider.event.<action>` 时
|
||||
(例如 `github.workflow_run.completed` → `completed`)。
|
||||
- body 里 `action`、`state`、`conclusion`、`status` 这四个字段——
|
||||
**必须是 JSON 字符串**。布尔(`{"action": true}`)或数字都不算
|
||||
候选,所以 `event=trigger, action=true` 的过滤规则**永远命中不了**
|
||||
`{"trigger": true}` 这种 body,因为 `true` 是 bool 不是字符串。
|
||||
|
||||
**常见误区。** 一条 `Event name: trigger` / `Actions: true` 的规则
|
||||
**不是**「body 里出现 `trigger: true` 就放行」的意思——事件过滤匹配的是
|
||||
**推断出来的事件和 action**,不是任意 body 字段。要命中它,请用
|
||||
`X-Event-Type` 头发送 `trigger.true`,或者用上面的 body envelope。
|
||||
保存时带空格的值(例如 `" workflow_run "`)会被原样保存,但永远命中
|
||||
不了——保存前请先 trim。
|
||||
|
||||
#### 快速验证
|
||||
|
||||
配好过滤后,可以用 `curl` 同时验证「命中」和「被过滤」两条路径:
|
||||
|
||||
```bash
|
||||
# 命中 —— 请求头给出 event=workflow_run,body 给出 action=completed
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-GitHub-Event: workflow_run' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"completed"}'
|
||||
# → 200 {"status":"accepted", ...}
|
||||
|
||||
# 被过滤 —— 同样的事件,但 action 不在白名单
|
||||
curl -X POST "$MULTICA_WEBHOOK_URL" \
|
||||
-H 'X-GitHub-Event: workflow_run' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"action":"in_progress"}'
|
||||
# → 200 {"status":"ignored","reason":"event_filtered"}
|
||||
```
|
||||
|
||||
### URL 即 bearer secret
|
||||
|
||||
生成的 URL **就是凭证**,谁拿到都能触发这个 Autopilot。请按 token 对待:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { X, Plus, Filter } from "lucide-react";
|
||||
import { X, Plus, Filter, ExternalLink } from "lucide-react";
|
||||
import { cn } from "@multica/ui/lib/utils";
|
||||
import type { WebhookEventFilter } from "@multica/core/types";
|
||||
import { useT } from "../../i18n";
|
||||
@@ -15,9 +15,12 @@ export function WebhookEventFilterSection({
|
||||
filters,
|
||||
onChange,
|
||||
}: WebhookEventFilterSectionProps) {
|
||||
const { t } = useT("autopilots");
|
||||
const { t, i18n } = useT("autopilots");
|
||||
const [newEvent, setNewEvent] = useState("");
|
||||
const [newActions, setNewActions] = useState("");
|
||||
const docsHref = i18n.language?.startsWith("zh")
|
||||
? `https://multica.ai/docs/zh/autopilots#${encodeURIComponent("事件过滤")}`
|
||||
: "https://multica.ai/docs/autopilots#event-filters";
|
||||
|
||||
const addFilter = () => {
|
||||
const event = newEvent.trim();
|
||||
@@ -42,6 +45,16 @@ export function WebhookEventFilterSection({
|
||||
<div className="flex items-center gap-1.5 text-[11px] font-semibold tracking-[0.08em] text-muted-foreground uppercase">
|
||||
<Filter className="size-3" />
|
||||
{t(($) => $.dialog.event_filter_label)}
|
||||
<a
|
||||
href={docsHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={t(($) => $.dialog.event_filter_docs_link_label)}
|
||||
title={t(($) => $.dialog.event_filter_docs_link_label)}
|
||||
className="ml-0.5 inline-flex items-center text-muted-foreground/80 hover:text-foreground transition-colors"
|
||||
>
|
||||
<ExternalLink className="size-3" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{filters.length > 0 && (
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"webhook_created_warning": "Treat this URL like a password. Anyone who has it can trigger the autopilot.",
|
||||
"webhook_created_done": "Done",
|
||||
"event_filter_label": "Event filters",
|
||||
"event_filter_docs_link_label": "Learn how event filters work",
|
||||
"event_filter_event_placeholder": "e.g. workflow_run",
|
||||
"event_filter_actions_placeholder": "completed, failed",
|
||||
"event_filter_hint": "Only process webhooks matching these events. Leave empty to accept all.",
|
||||
|
||||
@@ -259,6 +259,7 @@
|
||||
"webhook_created_warning": "请把该 URL 当作密码对待,任何拿到它的人都能触发该 Autopilot。",
|
||||
"webhook_created_done": "完成",
|
||||
"event_filter_label": "事件过滤",
|
||||
"event_filter_docs_link_label": "了解事件过滤的工作方式",
|
||||
"event_filter_event_placeholder": "例如 workflow_run",
|
||||
"event_filter_actions_placeholder": "completed, failed",
|
||||
"event_filter_hint": "只处理匹配这些事件的 webhook。留空则接受所有事件。",
|
||||
|
||||
Reference in New Issue
Block a user