diff --git a/CHANGELOG.md b/CHANGELOG.md index 237f01245fd..03619fa7905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai - CLI/Config validation and routing hardening: dedupe `openclaw config validate` failures to a single authoritative report, expose allowed-values metadata/hints across core Zod and plugin AJV validation (including `--json` fields), sanitize terminal-rendered validation text, and make command-path parsing root-option-aware across preaction/route/lazy registration (including routed `config get/unset` with split root options). Thanks @gumadeiras. - Models/config env propagation: apply `config.env.vars` before implicit provider discovery in models bootstrap so config-scoped credentials are visible to implicit provider resolution paths. (#32295) Thanks @hsiaoa. - Hooks/runtime stability: keep the internal hook handler registry on a `globalThis` singleton so hook registration/dispatch remains consistent when bundling emits duplicate module copies. (#32292) Thanks @Drickon. +- Hooks/plugin context parity: ensure `llm_input` hooks in embedded attempts receive the same `trigger` and `channelId`-aware `hookCtx` used by the other hook phases, preserving channel/trigger-scoped plugin behavior. (#28623) Thanks @davidrudduck and @vincentkoc. - Restart sentinel formatting: avoid duplicate `Reason:` lines when restart message text already matches `stats.reason`, keeping restart notifications concise for users and downstream parsers. (#32083) Thanks @velamints2. - Voice-call/Twilio signature verification: retry signature validation across deterministic URL port variants (with/without port) to handle mixed Twilio signing behavior behind reverse proxies and non-standard ports. (#25140) Thanks @drvoss. - Hooks/webhook ACK compatibility: return `200` (instead of `202`) for successful `/hooks/agent` requests so providers that require `200` (for example Forward Email) accept dispatched agent hook deliveries. (#28204) Thanks @Glucksberg. diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 9d440bda6eb..3c5d5a67f6f 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -263,6 +263,8 @@ export async function runEmbeddedPiAgent( sessionId: params.sessionId, workspaceDir: resolvedWorkspace, messageProvider: params.messageProvider ?? undefined, + trigger: params.trigger, + channelId: params.messageChannel ?? params.messageProvider ?? undefined, }; if (hookRunner?.hasHooks("before_model_resolve")) { try { @@ -715,6 +717,7 @@ export async function runEmbeddedPiAgent( const attempt = await runEmbeddedAttempt({ sessionId: params.sessionId, sessionKey: params.sessionKey, + trigger: params.trigger, messageChannel: params.messageChannel, messageProvider: params.messageProvider, agentAccountId: params.agentAccountId, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 64a8f2fd2cf..d1b158eee9f 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -1356,6 +1356,8 @@ export async function runEmbeddedAttempt( sessionId: params.sessionId, workspaceDir: params.workspaceDir, messageProvider: params.messageProvider ?? undefined, + trigger: params.trigger, + channelId: params.messageChannel ?? params.messageProvider ?? undefined, }; const hookResult = await resolvePromptBuildHookResult({ prompt: params.prompt, diff --git a/src/agents/pi-embedded-runner/run/params.ts b/src/agents/pi-embedded-runner/run/params.ts index 7362f7fcdc3..647d9dd4a32 100644 --- a/src/agents/pi-embedded-runner/run/params.ts +++ b/src/agents/pi-embedded-runner/run/params.ts @@ -26,6 +26,8 @@ export type RunEmbeddedPiAgentParams = { messageChannel?: string; messageProvider?: string; agentAccountId?: string; + /** What initiated this agent run: "user", "heartbeat", "cron", or "memory". */ + trigger?: string; /** Delivery target (e.g. telegram:group:123:topic:456) for topic/thread routing. */ messageTo?: string; /** Thread/topic identifier for routing replies to the originating thread. */ diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 70d7becf762..ea8c25c1e52 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -295,6 +295,7 @@ export async function runAgentTurnWithFallback(params: { }); return runEmbeddedPiAgent({ ...embeddedContext, + trigger: params.isHeartbeat ? "heartbeat" : "user", groupId: resolveGroupSessionKey(params.sessionCtx)?.id, groupChannel: params.sessionCtx.GroupChannel?.trim() ?? params.sessionCtx.GroupSubject?.trim(), diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts index 985a5e2ee6c..e14946ce8c2 100644 --- a/src/auto-reply/reply/agent-runner-memory.ts +++ b/src/auto-reply/reply/agent-runner-memory.ts @@ -487,6 +487,7 @@ export async function runMemoryFlushIfNeeded(params: { ...embeddedContext, ...senderContext, ...runBaseParams, + trigger: "memory", prompt: resolveMemoryFlushPromptForRun({ prompt: memoryFlushSettings.prompt, cfg: params.cfg, diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts index d36080f4443..2a9cf9a550f 100644 --- a/src/auto-reply/reply/followup-runner.ts +++ b/src/auto-reply/reply/followup-runner.ts @@ -157,6 +157,8 @@ export function createFollowupRunner(params: { sessionId: queued.run.sessionId, sessionKey: queued.run.sessionKey, agentId: queued.run.agentId, + trigger: "user", + messageChannel: queued.originatingChannel ?? undefined, messageProvider: queued.run.messageProvider, agentAccountId: queued.run.agentAccountId, messageTo: queued.originatingTo, diff --git a/src/commands/agent.ts b/src/commands/agent.ts index b0d3f3f09ce..f1258cb8ced 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -279,6 +279,7 @@ function runAgentAttempt(params: { sessionId: params.sessionId, sessionKey: params.sessionKey, agentId: params.sessionAgentId, + trigger: "user", messageChannel: params.messageChannel, agentAccountId: params.runContext.accountId, messageTo: params.opts.replyTo ?? params.opts.to, diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 623cc6e3eb2..028b2e3ce36 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -490,6 +490,7 @@ export async function runCronIsolatedAgentTurn(params: { sessionId: cronSession.sessionEntry.sessionId, sessionKey: agentSessionKey, agentId, + trigger: "cron", messageChannel, agentAccountId: resolvedDelivery.accountId, sessionFile, diff --git a/src/plugins/types.ts b/src/plugins/types.ts index 2dbb0a23bfc..fb2f645a233 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -340,6 +340,10 @@ export type PluginHookAgentContext = { sessionId?: string; workspaceDir?: string; messageProvider?: string; + /** What initiated this agent run: "user", "heartbeat", "cron", or "memory". */ + trigger?: string; + /** Channel identifier (e.g. "telegram", "discord", "whatsapp"). */ + channelId?: string; }; // before_model_resolve hook