---
title: "触发器"
description: "触发器会按计划、通过 webhook、根据应用事件或通过监控器启动会话。"
---

触发器会在没有人员参与的情况下启动一个[会话](/docs/zh/work/sessions)。使用触发器自动执行定期或事件驱动的工作。

如果要稍后跟进某个会话中的任务，例如“明天检查对方是否回复”，请改用[提醒](/docs/zh/connect/reminders)。提醒是限定于该会话的触发器。它保存在数据库中，而不是 `beeuni.yaml`。

<a id="trigger-types"></a>

## 触发器类型

Beeuni 支持四种触发器类型。

- **cron** — 按你设置的计划运行。
- **webhook** — 外部服务向项目的 webhook URL 发送签名请求时运行。
- **event** — 连接的应用中发生事件时运行，例如新建拉取请求或收到新邮件。参阅[应用事件触发器](#app-event-triggers)。
- **monitor** — 全天候运行仓库中的命令。命令输出到 stdout 的每一行都会触发运行。此功能处于实验阶段：参阅[监控器](#monitors)。

你可以在项目清单 `beeuni.yaml` 中定义触发器。每个触发器都包含一个提示词，它会显示为触发后会话的第一条消息。运行时状态（例如上次触发时间和状态）存储在清单之外的数据库中。触发器运行不会创建提交。

通过控制面板创建、更新或删除触发器，会直接写入默认分支，不会经过[变更请求](/docs/zh/work/change-requests)（CR）。在会话中编辑 `beeuni.yaml` 并运行 `beeuni ship`，则会照常经过分支和 CR 流程。

<a id="set-up-a-cron-trigger"></a>

## 设置 cron 触发器

1. **添加触发器**

    ```sh
    beeuni triggers add daily-digest --type cron \
      --cron "0 0 9 * * 1-5" --timezone America/Los_Angeles \
      --prompt "Summarize yesterday's activity and save it as a daily note."
    ```

    `cron` 是一个 6 字段表达式：秒、分、时、日、月、星期。触发器最多每分钟运行一次：`0 */30 * * * *` 表示每 30 分钟运行一次，而 `*/30 * * * * *`（每 30 秒运行一次）会被拒绝。此命令只会编辑本地 `beeuni.yaml`。

2. **发布**

    ```sh
    beeuni ship
    ```

    `beeuni ship` 会提交 `beeuni.yaml` 并推送它。变更合并到项目的默认分支后，计划就会生效。

3. **确认它是否运行**

    ```sh
    beeuni triggers ls
    ```

    列表会显示每个触发器的 slug、状态和上次触发时间。若要立即触发，而不是等待计划时间，请运行 `beeuni triggers fire daily-digest`。此命令会等待运行结果；如果运行失败，则以非零状态码退出并显示失败文本；`--wait <dur>` 用于限制等待时间（默认为 `90s`）。

<a id="set-up-a-webhook-trigger"></a>

## 设置 webhook 触发器

webhook 触发器需要一个密钥。Beeuni 会用它验证每个传入请求的签名。

1. **添加密钥**

    ```sh
    beeuni secrets set WEBHOOK_SECRET=<a-random-value>
    ```

    有关密钥的更多信息，请参阅[密钥](/docs/zh/project/secrets)。

2. **添加触发器**

    ```sh
    beeuni triggers add new-lead --type webhook \
      --secret-env WEBHOOK_SECRET \
      --prompt "A new lead arrived: {{ body.name }} ({{ body.email }}). Add it to the CRM."
    ```

    `--secret-env` 指定用于签署发往此触发器请求的密钥。

3. **发布**

    ```sh
    beeuni ship
    ```

4. <a id="send-it-a-request"></a>

    ### 向它发送请求

    Beeuni 会根据项目 ID 和触发器的 slug 构建 webhook URL：

    ```
    POST /v1/webhooks/projects/<project-id>/<slug>
    ```

    从外部服务向此 URL 发送带签名的 `POST` 请求。Beeuni 会根据 `WEBHOOK_SECRET` 验证签名，然后启动一个会话，提示词中可使用请求正文。确切的标头和格式请参阅下文的 [Webhook 签名](#webhook-signature)。
默认情况下，每次触发都会在新分支上启动一个全新会话。触发器也可以复用或固定会话；webhook 触发器还可以筛选要启动会话的负载——请参阅下文的[会话策略](#session-strategy)和[负载模板](#payload-templating)。

<a id="app-event-triggers"></a>

## 应用事件触发器

应用事件触发器会在应用事件发生时运行代理：例如拉取请求开启、收到邮件或问题状态发生变化。你无需配置 webhook、密钥或签名。Beeuni 会为你订阅事件，无需手动签名。

事件来自**事件源**，即传递应用事件的适配器。`GITHUB_PULL_REQUEST_CREATED` 等事件 ID 标识该事件源支持的事件。使用事件目录选择连接器对应的事件。

<a id="set-up-in-the-web-app"></a>

### 在 Web 应用中设置

1. 打开项目，然后选择 **Triggers** 和 **New trigger**。
2. 选择 **App event**。
3. 选择一个应用，然后在其中选择一个连接器及其共享账号，或连接另一个账号。除非你另选账号，否则会使用标记为 **Default** 的账号。
4. 选择一个事件。其设置表单（例如仓库设置）会显示在下方。
5. 编写提示词并保存。

当徽章显示 **Live** 时，触发器即已生效。其他徽章包括 **Activating**、**Needs connection** 和 **Error**。它们分别对应下文的 `pending`、`needs_connection` 和 `error` 状态。

<a id="see-every-app-event-trigger"></a>

### 查看所有应用事件触发器

在 Web 应用中，打开 **Triggers** 并选择 **App events**。筛选栏显示 **All**、**Schedules**、**App events** 和 **Webhooks**，每项后面都带有数量。所选项会保存在地址中的 `?type=event`，因此你可以分享链接。列表按应用分组。列表下方的 **Apps with events** 会列出所有可启动代理的应用，并优先显示项目已经使用的应用。选择一个应用即可打开该应用的新建触发器表单。

连接器窗口中有一个 **Triggers** 选项卡，其中显示该连接器上的应用事件。只有包含事件的应用才会显示此选项卡。

使用 CLI：

```bash
beeuni triggers ls --type event                 # every app event trigger, grouped by app
beeuni triggers ls --connector github-work      # the app events on one connector
beeuni triggers ls --type event --json          # the same list as JSON
```

`--type` 也接受 `cron`、`webhook` 和 `monitor`。`--connector` 和 `--type` 可以组合使用。

<a id="set-up-with-the-cli"></a>

### 使用 CLI 设置

代理或人员按顺序执行这些步骤。每一步都会输出下一步所需的信息。

```sh
beeuni triggers events --apps
beeuni triggers events --app github
beeuni connectors add github --provider composio --app github --apply
beeuni connectors connect github --owner project
beeuni triggers events --connector github
beeuni triggers events --connector github --event GITHUB_PULL_REQUEST_CREATED
beeuni triggers add pr-review --type event \
  --connector github --event GITHUB_PULL_REQUEST_CREATED \
  --config repo=hekmat/api \
  --prompt "Review {{ event.data.html_url }}" --apply
beeuni triggers info pr-review
```

1. `triggers events --apps` 会列出可触发事件的应用。应用按事件源分组（`source: composio`）。每个应用都会列出其连接器（配置）及其共享账号。`STATE` 列显示 `connected` 或 `needs account`；没有连接器的应用会列在 `No connector yet` 下。
2. 如果应用没有连接器，`connectors add` 会添加连接器。如果已设置 `CONNECTOR`，则跳过此步骤。
3. `connectors connect <slug> --owner project` 会输出一个链接。由人员打开链接来连接共享账号。成员的私人账号不算共享账号。再次运行此命令可添加第二个共享账号，然后使用 `connectors rename <id> <label>` 为其添加标签。
4. `triggers events --connector <slug>` 会列出事件。添加 `--event <TYPE>` 可查看某个事件的设置及其 `{{ event.data.* }}` 变量。阅读每项设置的说明：有些事件要求在一个设置中填写完整名称，例如 `repo: hekmat/api`，并且不接受将同一值拆分填写到 `owner` 和 `repo` 中。
5. `triggers add --apply` 会立即创建触发器，并输出其状态和下一步操作。缺少设置或设置无效时，命令会以代码 2 退出并列出所有问题。如果没有 `--apply`，CLI 会将配置块写入本地 `beeuni.yaml`；运行 `beeuni ship` 以应用它。
6. `triggers info <slug>` 会显示状态。重复操作，直到状态显示 `live`。

`--source <adapter>` 用于设置事件源，可选：如果不指定，触发器会使用连接器的提供方。连接器必须属于该事件源。对于属于其他提供方的连接器，`composio` 事件源会显示 `error`：`Connector "<slug>" is a <provider> connector; source "composio" needs a composio connector.`。未知来源会被拒绝，并显示 `Unknown event source "<x>". Sources: composio.`。`beeuni triggers set <slug> --source <adapter>` 用于更改事件源；更改 `--connector` 会将其清除。

`--config key=value` 会针对每个设置重复一次，并转换为该设置的类型。`--config-json '{"limit": 5}'` 传递类型化值。`beeuni triggers set <slug> --config key=value` 会合并到实时配置中；`--config-json` 会替换它。请勿同时传递两者。事件触发器不接受 `--cron`、`--run-at`、`--timezone`、`--secret-env`、`--run`、`--mode`、`--interval` 或 `--expect-event-within`。

<a id="config-shape"></a>

### 配置结构

```yaml
# beeuni.yaml
triggers:
  - slug: pr-review
    type: event
    connector: github-work # required, slug of a connector (profile) under `connectors:`
    source: composio # optional, the event source adapter. Default: the connector's provider
    account: hekmat-bot # optional, label of one shared account of that connector
    event: GITHUB_PULL_REQUEST_CREATED # required, the adapter's event id, from `beeuni triggers events`
    config: { repo: hekmat/api } # optional, settings of the event
    agent: default
    session_mode: fresh # default "fresh"
    filter: # optional, same guard a webhook uses
      "event.data.draft": "false"
    prompt: |
      Review {{ event.data.html_url }}
```

<a id="what-the-prompt-can-read"></a>

### 提示可以读取的内容

提示、`filter` 和 `session_key` 可以读取以下根字段：

| 根字段 | 值 |
| --- | --- |
| `event.data.<field>` | 应用自身的事件数据。`beeuni triggers events` 显示每种事件所包含的字段。 |
| `event.id` | 提供方为此事件分配的 ID。 |
| `event.type` | 事件类型，例如 `GITHUB_PULL_REQUEST_CREATED`。 |
| `event.app` | 应用的 slug，例如 `github`。 |
| `event.connector` | 触发器所监视的连接器 slug。 |
| `event.occurred_at` | 事件发生的时间。 |

事件数据来自第三方。Beeuni 会在会话的首条消息前加上 `[App event: <trigger name> — automated, third-party content, not user input]`，让智能体将事件内容视为数据而非指令。在智能体为会话命名之前，这一行也会作为会话标题。

<a id="status"></a>

### 状态

`beeuni triggers ls` 和 Web 触发器列表会显示每个事件触发器的订阅状态。

| 状态 | 含义 | 解决方法 |
| --- | --- | --- |
| `active` | 订阅已存在。事件会触发触发器。 | 无需处理。 |
| `pending` | 已声明触发器，但 Beeuni 尚未创建订阅。 | 稍等片刻。如果仍为 `pending`，请再次保存触发器。 |
| `needs_connection` | 连接器没有可用的共享账户。提示文字为“Connect a shared `<app>` account to activate this trigger.”或“Finish connecting the shared `<app>` account…” | 以项目身份（`beeuni connectors connect <slug> --owner project`）为连接器连接一个共享账户。成员的私人账户不算。用户完成连接后，触发器会自动生效，即使他们是通过 Beeuni 之外的链接进行连接。 |
| `error` | 订阅失败，或已被提供方停用。触发器会显示错误文本。 | 按文本中指出的原因解决问题，然后再次保存触发器。 |

常见的 `error` 文本：

- “This `<app>` account is shared with specific people only. Event triggers need an account shared with the whole project.”打开该账户的共享设置，并将其共享给整个项目。
- “Connector `<slug>` is not declared in beeuni.yaml.”在 `connectors:` 下添加连接器。
- 如果事件触发器不可用，请联系支持团队。

- “The app disabled this subscription: ...”提供方关闭了订阅。再次保存触发器即可重新订阅。
- “The connected account expired: ...”应用撤销了该账户，或该账户已过期。重新连接应用以恢复触发器。

<a id="rules"></a>

### 规则

- **连接器和账户。** *应用*是 GitHub 这类服务。*连接器*是 `beeuni.yaml` 中的配置；多个连接器可以共用一个应用。*账户*是连接器下的一个已连接登录账户。触发器指定一个连接器。除非设置了 `account: <label>`，否则触发器使用该连接器的默认共享账户。只有在连接器包含多个共享账户时才设置 `account`。在 CLI 中，`--account <label>` 用于设置它，`--default-account`（在 `triggers set` 上）用于清除它。若标签与任何共享账户都不匹配，则会显示 `needs_connection`，并提示“Connect a shared `<app>` account labelled ”`<label>`“ on `<connector>`.”
- **单一事件源账户。** 只有对整个项目开放的项目共享账户才能为触发器提供事件。触发器会在无人参与的情况下运行，事件数据会进入会话提示。仅共享给指定人员的账户或私人账户都无法为触发器提供事件。
- **每个事件只触发一次。** 每个提供方事件只会触发一次。重新投递的事件不会启动第二个会话。同一事件上的两个触发器各自只会触发一次。
- **暂停即停止。** 触发器或项目暂停时会跳过事件。被跳过的事件不会排队。
- **编辑会重新订阅。** 更改 `connector`、`source`、`account`、`event` 或 `config` 会替换订阅。删除触发器会移除订阅。

<a id="monitors"></a>

## 监视器

**实验性功能。** 监视器仅在启用了 `monitors` 功能标志时运行。该标志默认关闭。关闭时，平台不会配置监视器虚拟机，也不会触发监视器事件。

监视器用于监视既不会推送 Webhook、也不适合按计划运行的对象：实时日志、队列深度、会变化的页面、价格。Beeuni 会在项目的监视器虚拟机中 24/7 运行你的命令——每个项目一个持久化微型虚拟机，隔离边界与会话沙盒相同，也使用相同的项目密钥。由确定性代码负责监视；只有命令输出一行时，智能体才会唤醒。

四条规则定义了其约定：

- **标准输出的每一行都是一个事件，除此之外都不是。** 标准错误是诊断信息：可在监视器日志中查看，但不会触发事件。
- 每一行都会通过 Webhook 已使用的路径恰好触发一次：`filter` → 提示模板 → `session_mode`。
- **监视器不会无声失败。** 进程退出、重启预算耗尽，以及静默时间超过 `expect_event_within`，都会在同一事件流中触发由平台编写的生命周期事件。
- 监视器上的 `session_mode` 默认值为 `reuse`，而非 `fresh`。监视器按设计会重复触发，因此 `fresh` 会为每个事件创建一个会话。

1. **添加监视器**

    ```sh
    beeuni triggers add checkout-errors --type monitor \
      --run "./monitors/checkout-errors.ts" \
      --mode poll --interval 60s --expect-event-within 24h \
      --prompt "Checkout monitor emitted: {{ line }}"
    ```

    `--mode poll` 会每隔 `--interval` 重新运行命令，并要求命令退出。`--mode stream` 则只运行一次并保持运行；流式模式不接受 `--interval`。这两种模式都会产生输出行，下游无法区分它们。监视器不接受 `cron`、`run_at`、`timezone` 和 `secret_env`。

2. **发布**

    ```sh
    beeuni ship
    ```

    更新合并到默认分支后，平台便会启动监视器虚拟机。

3. **确认运行状态**

    ```sh
    beeuni triggers ls
    beeuni triggers info checkout-errors
    ```

    `ls` 会显示监视器的模式和间隔，而 cron 会显示其计划。`info` 会显示 `run`、`mode`、`interval` 和 `expect_event_within`。

<a id="monitor-limits"></a>

### 监视器限制

以下所有限制均由平台强制执行。

| 限制 | 值 |
| --- | --- |
| 每个项目的监视器数 | 最多启用 10 个 |
| 轮询间隔 | 至少 30s |
| `expect_event_within` | 至少 5m |
| 每个监视器的事件速率 | 持续速率为 60/小时，突发速率为 30。超出时会暂停该监视器 10 分钟；24 小时内暂停 3 次后会将其停用。 |
| 行长度 | 8 KiB，超出部分会被截断，并附加 `truncated: true` 标记 |
| 重启预算 | 10 分钟内重启 5 次，之后触发生命周期事件 `restart_budget_exhausted`，并进入 15 分钟退避期 |
| 事件保留期 | 30 天 |
| 每月虚拟机预算 | 默认 $75。超出后，虚拟机会停止，并触发一次生命周期事件 `budget_exceeded`。 |

监视器虚拟机需要支持持久化沙盒的提供方。如果项目的提供方无法运行此类沙盒，`monitors` 标志会报告该功能不可用。

<a id="config-shape-1"></a>

## 配置结构

```yaml
# beeuni.yaml
triggers:
  - slug: daily-digest # required, lowercase + dashes, unique per project
    name: Daily digest # optional, defaults to slug
    type: cron # "cron" | "webhook" | "event" | "monitor", required
    agent: beeuni # optional, defaults to "default"
    model: anthropic/claude-sonnet-4-5 # optional, resolves at fire time if unset
    enabled: true # optional, default true
    cron: "0 0 9 * * 1-5" # 6-field expression, mutually exclusive with run_at
    timezone: America/Los_Angeles # IANA name, default UTC
    session_mode: reuse # "fresh" | "reuse" | "pinned" | "keyed", default "fresh"
    filter: # optional, webhook payload guard
      "body.data.direction": "inbound"
    prompt: "Summarize {{ body.text }}" # required, template string
```

监视器会将计划字段替换为其命令和模式：

```yaml
# beeuni.yaml
triggers:
  - slug: checkout-errors
    type: monitor
    run: ./monitors/checkout-errors.ts # required, repo-relative command
    mode: poll # "poll" | "stream", required
    interval: 60s # required on poll, invalid on stream
    expect_event_within: 24h # optional silence watchdog
    agent: oncall
    session_mode: reuse # the monitor default
    filter: # optional, same guard a webhook uses
      "line.severity": "error"
    prompt: "Checkout monitor emitted: {{ line }}"
```

事件触发器会将计划字段替换为其监视的应用和事件。请参阅 [应用事件触发器](#app-event-triggers)。

旧版 `beeuni.toml` 在不同的容器中使用相同字段；请参阅 [旧版 TOML](/docs/zh/project/legacy-toml)。

<a id="fields"></a>

## 字段

| 字段 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `slug` | 是 | — | `[a-z0-9][a-z0-9_-]{0,127}`，每个项目中唯一。 |
| `type` | 是 | — | `cron`、`webhook` 或 `monitor`。 |
| `prompt` | 是 | — | 模板字符串。会作为会话的第一条消息呈现。 |
| `name` | 否 | `slug` | 人类可读的标签。 |
| `agent` | 否 | `default_agent` | 必须是 `agents:` 中的键。省略时使用 `default_agent`；不要写入字面值 `default`。 |
| `model` | 否 | 触发时解析 | 线缆格式为 `provider/model`，例如 `anthropic/claude-sonnet-4-5`。 |
| `enabled` | 否 | `true` | 当 `false` 时，调度器和 webhook 接收器会跳过此条目。 |
| `session_mode` | 否 | `fresh`；监视器上为 `reuse` | 请参阅[会话策略](#session-strategy)。 |
| `session_id` | `pinned` 必填 | — | 要重新提示的确切会话。 |
| `session_key` | `keyed` 必填 | — | 模板字符串。仅设置此项即表示 `session_mode: keyed`。 |
| `filter` | 否 | — | 点分路径 → 预期字符串。不匹配的 webhook 投递会返回 `200`，且不会触发会话。 |
| `cron` | 在 `type: cron` 上为 `cron`/`run_at` 之一 | — | 6 字段表达式：秒、分钟、小时、日、月、星期。最多每 60 秒触发一次；更快的表达式会导致 `beeuni validate` 和 API 失败。 |
| `run_at` | 在 `type: cron` 上为 `cron`/`run_at` 之一 | — | ISO-8601 时间戳。触发一次后进入休眠状态。 |
| `timezone` | 否，仅 cron | `UTC` | IANA 名称。 |
| `secret_env` | 必填，仅 webhook | — | 保存签名密钥的项目密钥名称。该密钥必须使用 `broker` 投递方式和 `connector` 消费者。 |
| `run` | 必填，仅监视器 | — | 相对于仓库的命令，其标准输出中的每一行都是一个事件。每行最多 1024 个字符。 |
| `mode` | 必填，仅监视器 | — | `poll` 每隔 `interval` 重新运行 `run`；`stream` 则运行一次并保持运行。 |
| `interval` | `mode: poll` 必填 | — | 时长字面值（`30s`、`5m`、`24h`、`7d`），最短为 `30s`。在 `mode: stream` 上无效。 |
| `expect_event_within` | 否，仅监视器 | — | 时长字面值，最短为 `5m`。静默时间超过此值会触发生命周期事件。 |

Cron 触发器需要 `cron` 或 `run_at`，不能同时设置。缺少 `secret_env` 的 webhook 触发器会被拒绝——不允许未经身份验证的 webhook。监视器需要 `run` 和 `mode`，并会直接拒绝 `cron`、`run_at`、`timezone` 和 `secret_env`——清单若声明了监视器运行器从不读取的计划，就是在撒谎。

创建或更新 webhook 触发器前，请先配置签名密钥：

```bash
beeuni secrets set WEBHOOK_SECRET=-
beeuni secrets delivery WEBHOOK_SECRET broker --consumer connector
```

通过标准输入将值传给第一条命令。如果该密钥之前使用过 sandbox 投递方式，请在更改投递方式后轮换密钥，因为现有 sandbox 可能仍保留旧值。

<a id="session-strategy"></a>

## 会话策略

`session_mode` 控制触发时重新提示哪个会话。Beeuni 会按以下顺序尝试各模式，每一步失败后都会继续尝试下一种。

1. **`pinned`** — 重新提示指定的 `session_id`。如果该会话已不存在或已失败，则继续尝试下一种模式。
2. **`keyed`** — 根据负载呈现 `session_key`，然后查找最近一个曾以该确切键标记且未失败的会话。如果键呈现为空，或找不到匹配会话，则继续使用新会话。绝不会改用其他键对应的会话。
3. **`reuse`** — 重新提示此触发器之前创建的最近一个未失败会话。固定会话不可用时，也会先回退到此模式，然后再继续尝试。
4. **`fresh`** — 创建新的 sandbox 和分支。这是默认模式，也是所有模式的最终回退方式。新会话会成为此触发器后续 `reuse` 和 `keyed` 触发时使用的会话。

如果 `reuse` 或 `keyed` 会话的历史记录在自动压缩后仍超出模型可处理范围，该会话就会被弃用。下一次触发时会启动新会话。其他任何失败都会保留该会话，供下一次触发使用。

如果频繁触发的 cron 不应反复发送不断增长的历史记录，可以按日历边界轮换会话：`session_key: "merge-{{ cron.scheduled_date }}"` 每个 UTC 日保留一个串行化会话，`{{ cron.scheduled_hour }}` 每个 UTC 小时保留一个。

<a id="failed-runs"></a>

## 运行失败

由触发器启动的运行以错误结束时，Beeuni 会在触发器上记录失败：

- `last_status` 变为 `failed`，`last_error` 保存失败原因，例如 `Out of credits: Payment Required: Insufficient credits.`
- Schedule 页面会将触发器标记为“上次运行未完成”，并在其面板中显示失败原因。
- 首次运行失败时，账户所有者会收到一条移动推送通知。之后的运行若失败，只会更新原因，不会再次推送。
- 只要触发器仍在继续触发，它就会保持 `failed`。再次触发并不能证明其运行正常。

下一次成功完成的运行会清除失败记录。你自行停止的运行、子代理以及重试的模型调用都不算运行结果。若在运行开始前触发失败，例如无法创建会话，则会在下一次成功触发时清除失败记录。

<a id="session-access"></a>

## 会话访问权限

触发器创建的会话默认使用私有策略。触发器代理的服务账户是这些会话的所有者——代理的身份是一个 `service_account` 主体，因此它可以像其他主体一样拥有会话并持有分配。项目管理员始终可以打开这些会话。账户所有者和账户管理员在每个项目中都拥有与管理员等同的访问权限，因此他们也可以打开这些会话。除非拥有上述角色之一，否则配置或手动触发触发器的人不会因此获得访问权限。

触发器设置提供三种策略：

- **触发器代理和项目管理员** — 普通项目成员无法打开会话。
- **指定队友** — 触发器代理、项目管理员以及选定的项目成员或账户组。
- **整个项目** — 所有项目成员。

此策略是在角色模型之上针对单个资源设置的可见性选项，而不是一种角色。它决定谁可以打开某个触发器的会话，但不会授予角色权限判定所拒绝的任何权限。请参阅[账户与访问权限](/docs/zh/accounts#per-feature-access-settings)。

访问策略属于账户本地运行时状态，不会写入可移植的 `beeuni.yaml` 清单，因为主体 ID 属于特定账户。请使用控制面板进行配置。
仅更新此策略不会创建 Git 提交。保存策略时，也会更新此前由该触发器创建的会话。

固定会话会保留自己的共享设置，因为该会话并非由触发器创建。如果固定会话不可用，且触发器创建了回退会话，则新会话会应用触发器策略。

<a id="payload-templating"></a>

## 负载模板

`prompt` 和 `session_key` 使用相同的引擎呈现：`{{ token.dotted.path }}`。缺失的值会呈现为空字符串——不会报错，也不会留下 `{{ }}`。对象和数组会呈现为 JSON。`session_key` 会先去除首尾空白，再截断至 512 个字符。

每次触发还会获得 `{{ trigger.slug }}`、`{{ trigger.type }}` 和 `{{ trigger.kind }}`（始终为 `git`）。其余变量取决于触发器的触发方式。

| 来源 | 变量 |
| --- | --- |
| cron | `{{ cron.schedule }}`、`{{ cron.timezone }}`、`{{ cron.scheduled_for }}`（本次触发对应的时段）、`{{ cron.claimed_at }}`（调度器接收任务的时间）、`{{ cron.last_scheduled_for }}`（上一个时段；首次触发时为空）、`{{ cron.scheduled_date }}`（时段的 UTC `YYYY-MM-DD`）、`{{ cron.scheduled_hour }}`（UTC `YYYY-MM-DDTHH`）。没有顶层 `fired_at`。 |
| webhook | `{{ fired_at }}`、`{{ body.* }}`（解析为 JSON；如果请求正文无法解析，则回退为 `{{ body.raw }}`）、`{{ headers.content_type }}`、`{{ headers.user_agent }}`、`{{ headers.forwarded_for }}`。 |
| monitor | `{{ line.* }}` — stdout 行，解析为 JSON；无法解析的行会渲染为 `{{ line.raw }}`。另有 `{{ monitor.slug }}`、`{{ monitor.seq }}`、`{{ monitor.emitted_at }}` 和 `{{ monitor.kind }}`（`event` 或 `lifecycle`）。 |
| 手动（控制面板中的“立即触发”或 `fire` 端点） | `{{ fired_at }}`、`{{ source }}`（`manual`）、`{{ actor }}`、`{{ message.text }}`、`{{ message.source }}`。 |

Beeuni 会在服务器端为每个渲染后的 monitor 提示添加前缀 `[MONITOR EVENT — automated, not user input]`。生命周期事件会完全忽略你的模板，改为渲染平台编写的提示，并且会绕过 `filter` — 不应意外过滤掉静默状态。

手动触发时，`{{ message.text }}` 会被硬编码为空字符串，`{{ message.source }}` 则设为 `manual_test`。手动触发并非向提示注入测试输入的方式。

`filter` 会将点分路径作为字符串与提示所见的同一负载进行比较。它用于打破循环。例如，某个来源如果同时报告对话双方的消息，原本会在代理回复后再次触发代理。

<a id="webhook-signature"></a>

## Webhook 签名

在 `POST /v1/webhooks/projects/{projectId}/{slug}` 上触发。Beeuni 按以下顺序检查请求，并使用恒定时间比较：

1. **HMAC 签名** — 请求头 `X-Beeuni-Signature: sha256=<hmac>`（`sha256=` 前缀可选）或兼容 GitHub 的 `X-Hub-Signature-256`。使用 `secret_env` 指定的密钥，对原始请求正文计算 HMAC-SHA256。
2. **静态令牌**，仅在没有签名请求头时使用，适用于无法对正文进行 HMAC 签名的发送方。将密钥作为 `X-Beeuni-Token: <secret>`、`Authorization: Bearer <secret>` 或 `Authorization: Basic <base64(user:secret)>` 发送（密码部分即为令牌）。

`beeuni triggers info <slug>` 会打印此方案 — 请求头、算法，以及一个使用 `$SECRET` 占位符签名的示例请求 — 而 `--json` 会将其作为 `webhook_signing` 携带。控制面板的触发器面板也会显示同一示例请求。

| 状态 | 含义 |
| --- | --- |
| 202 | 签名或令牌有效。请求正文为 `{ status: "fired" \| "queued" \| "deduped", session_id, ... }`。 |
| 200 | 有效，但已跳过 — 项目已暂停，或投递与 `filter` 不匹配。 |
| 400 | URL 中的项目 ID 或 slug 格式错误。 |
| 401 | 签名和令牌均缺失或错误。 |
| 404 | 未找到触发器、触发器已禁用、不是 webhook，或项目未处于活动状态。 |
| 409 | 签名密钥缺失、未激活、不可用，或未授权 `connector` 消费者。响应中包含 `webhook_secret_*` 代码和补救方法。 |
| 500 | 身份验证通过，但会话触发失败。 |

<a id="endpoints"></a>

## 端点

| 方法 + 路径 | 需要 | 说明 |
| --- | --- | --- |
| `GET /v1/projects/{projectId}/triggers` | `project.trigger.read` | 列出触发器、运行时状态（`last_fired_at`、`last_status`、`last_error`、`last_attempt_at`）和清单解析错误。错误条目会显示在 `errors[]` 中，不会影响其他触发器。 |
| `POST /v1/projects/{projectId}/triggers` | `project.trigger.create` | 创建触发器。直接提交到清单。 |
| `PATCH /v1/projects/{projectId}/triggers/{slug}` | `project.trigger.update` | 部分更新，合并到当前条目。 |
| `DELETE /v1/projects/{projectId}/triggers/{slug}` | `project.trigger.delete` | 同时清除触发器的运行时状态。 |
| `PATCH /v1/projects/{projectId}/triggers/activation` | `project.trigger.update` | 请求正文 `{ paused: boolean }`。请参阅[暂停和恢复](#pause-and-resume)。 |
| `POST /v1/projects/{projectId}/triggers/{slug}/fire` | `project.trigger.fire` | 手动触发。内置的项目 `member` 角色拥有 `project.trigger.fire`，因此普通成员也可以触发触发器。 |
| `POST /v1/webhooks/projects/{projectId}/{slug}` | 签名或令牌 | 公共 URL，由 webhook 密钥控制访问。 |

<a id="pause-and-resume"></a>

## 暂停和恢复

项目级开关可一次停止项目中的所有触发器，不受各触发器自身 `enabled` 字段的影响。暂停期间，调度器会跳过该项目，入站 webhook 会返回 `200` 和 `{ status: "skipped" }` — 不会触发任何会话。手动触发仍然有效。如果同一代码库在两个控制平面（例如开发环境和生产环境）上运行，可使用此功能避免 cron 重复触发。CLI：`beeuni triggers pause` 和 `beeuni triggers resume`。完整的 `beeuni triggers` 命令组请参阅 [CLI](/docs/zh/cli)。

<a id="limits-and-reliability"></a>

## 限制和可靠性

- 调度器大约每秒轮询一次（默认 1,000 毫秒；可通过 `BEEUNI_TRIGGER_SCHEDULER_INTERVAL_MS` 配置）。cron 任务不会恰好在对应时段开始：每个触发器都会获得一个固定偏移，最多为其周期的四分之一，且不超过 30 分钟，因此同一分钟内的多个触发器不会同时启动。触发器详情和 `GET /triggers`（`next_fire_at`）会显示下次运行的开始时间。
- 触发失败时，触发器的上次状态会设为失败，并附带错误信息。所有来源都适用：cron、手动、webhook、应用事件和 monitor。后续触发成功后，失败状态会清除。失败的运行会按[失败的运行](#failed-runs)中的说明清除。
- 默认情况下，每个项目最多可同时预配 3 个触发会话。超过此限制的触发会返回 `queued`（`202`）而不是失败，并会在其中一个会话完成预配后运行。同时运行的会话数量没有上限。
- 手动或 webhook 触发的超时时间为 45 秒。加载清单的超时时间为 30 秒。
- cron 触发以到期的计划时段为键，因此超时但实际已触发的任务在重试时不会重复。
- 每个 webhook 投递事件只运行一次。Beeuni 会按以下顺序从 `X-Beeuni-Delivery-Id`、`X-GitHub-Delivery`、`X-Gitlab-Event-UUID`、`Linear-Delivery`、`webhook-id` 或 `svix-id` 读取事件 ID；使用相同 ID 重试时会返回 `202 { status: "deduped" }`，且不会运行任何内容。`X-Request-Id` 不是事件 ID。如果没有事件 ID，那么 10 分钟内相同的正文和签名会被视为同一事件；之后则会再次运行，因此每次部署都发送相同静态正文的作业会在每次部署时运行。每个事件都应发送一个值不同的 `X-Beeuni-Delivery-Id`（示例请求使用 `$(uuidgen)`）。
- 如果先前的尝试因不可恢复的原因失败（例如账户额度用尽），或其会话已删除，那么使用相同 ID 重新投递时会再次运行。重复投递不会更改 `last_fired_at`。
