feat(hooks): add agentId support to webhook mappings (#13672)

* feat(hooks): add agentId support to webhook mappings

Allow webhook mappings to route hook runs to a specific agent via
the new `agentId` field. This enables lightweight agents with minimal
bootstrap files to handle webhooks, reducing token cost per hook run.

The agentId is threaded through:
- HookMappingConfig (config type + zod schema)
- HookMappingResolved + HookAction (mapping types)
- normalizeHookMapping + buildActionFromMapping (mapping logic)
- mergeAction (transform override support)
- HookAgentPayload + normalizeAgentPayload (direct /hooks/agent endpoint)
- dispatchAgentHook → CronJob.agentId (server dispatch)

The existing runCronIsolatedAgentTurn already supports agentId on
CronJob — this change simply wires it through from webhook mappings.

Usage in config:
  hooks.mappings[].agentId = "my-agent"

Usage via POST /hooks/agent:
  { "message": "...", "agentId": "my-agent" }

Includes tests for mapping passthrough and payload normalization.
Includes doc updates for webhook.md.

* fix(hooks): enforce webhook agent routing policy + docs/changelog updates (#13672) (thanks @BillChirico)

* fix(hooks): harden explicit agent allowlist semantics (#13672) (thanks @BillChirico)

---------

Co-authored-by: Pip <pip@openclaw.ai>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
This commit is contained in:
Bill Chirico
2026-02-10 19:23:58 -05:00
committed by GitHub
parent 45488e4ec9
commit ca629296c6
13 changed files with 448 additions and 2 deletions

View File

@@ -3176,12 +3176,17 @@ Defaults:
enabled: true,
token: "shared-secret",
path: "/hooks",
// Optional: restrict explicit `agentId` routing.
// Omit or include "*" to allow any agent.
// Set [] to deny all explicit `agentId` routing.
allowedAgentIds: ["hooks", "main"],
presets: ["gmail"],
transformsDir: "~/.openclaw/hooks",
mappings: [
{
match: { path: "gmail" },
action: "agent",
agentId: "hooks",
wakeMode: "now",
name: "Gmail",
sessionKey: "hook:gmail:{{messages[0].id}}",
@@ -3203,7 +3208,7 @@ Requests must include the hook token:
Endpoints:
- `POST /hooks/wake` → `{ text, mode?: "now"|"next-heartbeat" }`
- `POST /hooks/agent` → `{ message, name?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
- `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
- `POST /hooks/<name>` → resolved via `hooks.mappings`
`/hooks/agent` always posts a summary into the main session (and can optionally trigger an immediate heartbeat via `wakeMode: "now"`).
@@ -3214,6 +3219,8 @@ Mapping notes:
- `match.source` matches a payload field (e.g. `{ source: "gmail" }`) so you can use a generic `/hooks/ingest` path.
- Templates like `{{messages[0].subject}}` read from the payload.
- `transform` can point to a JS/TS module that returns a hook action.
- `agentId` can route to a specific agent; unknown IDs fall back to the default agent.
- `hooks.allowedAgentIds` restricts explicit `agentId` routing (`*` or omitted means allow all, `[]` denies all explicit routing).
- `deliver: true` sends the final reply to a channel; `channel` defaults to `last` (falls back to WhatsApp).
- If there is no prior delivery route, set `channel` + `to` explicitly (required for Telegram/Discord/Google Chat/Slack/Signal/iMessage/MS Teams).
- `model` overrides the LLM for this hook run (`provider/model` or alias; must be allowed if `agents.defaults.models` is set).