* 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>
133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
export type HookMappingMatch = {
|
|
path?: string;
|
|
source?: string;
|
|
};
|
|
|
|
export type HookMappingTransform = {
|
|
module: string;
|
|
export?: string;
|
|
};
|
|
|
|
export type HookMappingConfig = {
|
|
id?: string;
|
|
match?: HookMappingMatch;
|
|
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;
|
|
deliver?: boolean;
|
|
/** DANGEROUS: Disable external content safety wrapping for this hook. */
|
|
allowUnsafeExternalContent?: boolean;
|
|
channel?:
|
|
| "last"
|
|
| "whatsapp"
|
|
| "telegram"
|
|
| "discord"
|
|
| "irc"
|
|
| "googlechat"
|
|
| "slack"
|
|
| "signal"
|
|
| "imessage"
|
|
| "msteams";
|
|
to?: string;
|
|
/** Override model for this hook (provider/model or alias). */
|
|
model?: string;
|
|
thinking?: string;
|
|
timeoutSeconds?: number;
|
|
transform?: HookMappingTransform;
|
|
};
|
|
|
|
export type HooksGmailTailscaleMode = "off" | "serve" | "funnel";
|
|
|
|
export type HooksGmailConfig = {
|
|
account?: string;
|
|
label?: string;
|
|
topic?: string;
|
|
subscription?: string;
|
|
pushToken?: string;
|
|
hookUrl?: string;
|
|
includeBody?: boolean;
|
|
maxBytes?: number;
|
|
renewEveryMinutes?: number;
|
|
/** DANGEROUS: Disable external content safety wrapping for Gmail hooks. */
|
|
allowUnsafeExternalContent?: boolean;
|
|
serve?: {
|
|
bind?: string;
|
|
port?: number;
|
|
path?: string;
|
|
};
|
|
tailscale?: {
|
|
mode?: HooksGmailTailscaleMode;
|
|
path?: string;
|
|
/** Optional tailscale serve/funnel target (port, host:port, or full URL). */
|
|
target?: string;
|
|
};
|
|
/** Optional model override for Gmail hook processing (provider/model or alias). */
|
|
model?: string;
|
|
/** Optional thinking level override for Gmail hook processing. */
|
|
thinking?: "off" | "minimal" | "low" | "medium" | "high";
|
|
};
|
|
|
|
export type InternalHookHandlerConfig = {
|
|
/** Event key to listen for (e.g., 'command:new', 'session:start') */
|
|
event: string;
|
|
/** Path to handler module (absolute or relative to cwd) */
|
|
module: string;
|
|
/** Export name from module (default: 'default') */
|
|
export?: string;
|
|
};
|
|
|
|
export type HookConfig = {
|
|
enabled?: boolean;
|
|
env?: Record<string, string>;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
export type HookInstallRecord = {
|
|
source: "npm" | "archive" | "path";
|
|
spec?: string;
|
|
sourcePath?: string;
|
|
installPath?: string;
|
|
version?: string;
|
|
installedAt?: string;
|
|
hooks?: string[];
|
|
};
|
|
|
|
export type InternalHooksConfig = {
|
|
/** Enable hooks system */
|
|
enabled?: boolean;
|
|
/** Legacy: List of internal hook handlers to register (still supported) */
|
|
handlers?: InternalHookHandlerConfig[];
|
|
/** Per-hook configuration overrides */
|
|
entries?: Record<string, HookConfig>;
|
|
/** Load configuration */
|
|
load?: {
|
|
/** Additional hook directories to scan */
|
|
extraDirs?: string[];
|
|
};
|
|
/** Install records for hook packs or hooks */
|
|
installs?: Record<string, HookInstallRecord>;
|
|
};
|
|
|
|
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;
|
|
mappings?: HookMappingConfig[];
|
|
gmail?: HooksGmailConfig;
|
|
/** Internal agent event hooks */
|
|
internal?: InternalHooksConfig;
|
|
};
|