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
+7
View File
@@ -14,6 +14,8 @@ export type HookMappingConfig = {
action?: "wake" | "agent";
wakeMode?: "now" | "next-heartbeat";
name?: string;
/** Route this hook to a specific agent (unknown ids fall back to the default agent). */
agentId?: string;
sessionKey?: string;
messageTemplate?: string;
textTemplate?: string;
@@ -115,6 +117,11 @@ export type HooksConfig = {
enabled?: boolean;
path?: string;
token?: string;
/**
* Restrict explicit hook `agentId` routing to these agent ids.
* Omit or include `*` to allow any agent. Set `[]` to deny all explicit `agentId` routing.
*/
allowedAgentIds?: string[];
maxBodyBytes?: number;
presets?: string[];
transformsDir?: string;
+1
View File
@@ -12,6 +12,7 @@ export const HookMappingSchema = z
action: z.union([z.literal("wake"), z.literal("agent")]).optional(),
wakeMode: z.union([z.literal("now"), z.literal("next-heartbeat")]).optional(),
name: z.string().optional(),
agentId: z.string().optional(),
sessionKey: z.string().optional(),
messageTemplate: z.string().optional(),
textTemplate: z.string().optional(),
+1
View File
@@ -301,6 +301,7 @@ export const OpenClawSchema = z
enabled: z.boolean().optional(),
path: z.string().optional(),
token: z.string().optional(),
allowedAgentIds: z.array(z.string()).optional(),
maxBodyBytes: z.number().int().positive().optional(),
presets: z.array(z.string()).optional(),
transformsDir: z.string().optional(),