From f33dc13af44552fc752b9d5de74a9412fdf69186 Mon Sep 17 00:00:00 2001 From: zhi Date: Thu, 26 Feb 2026 08:56:24 +0000 Subject: [PATCH] fix: extract senderId from event.prompt instead of ctx in hooks Root cause: PluginHookAgentContext in before_model_resolve only has agentId, sessionKey, sessionId, workspaceDir, messageProvider. senderId, channelId, input are NOT available in this hook phase. The plugin was reading ctx.senderId (undefined) -> inHumanList=false for ALL Discord sessions -> shouldUseNoReply=true -> all silenced. Fix: use event.prompt which contains the full user message including the 'Conversation info (untrusted metadata)' JSON block, and extract sender_id from there. Same fix applied to before_prompt_build. --- plugin/index.ts | 64 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/plugin/index.ts b/plugin/index.ts index c77f9a5..7f8ce75 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -356,32 +356,47 @@ export default { } }); - api.on("before_model_resolve", async (_event, ctx) => { + api.on("before_model_resolve", async (event, ctx) => { const key = ctx.sessionKey; if (!key) return; const live = getLivePluginConfig(api, baseConfig as WhisperGateConfig) as WhisperGateConfig & DebugConfig; ensurePolicyStateLoaded(api, live); + // In before_model_resolve, ctx only has: agentId, sessionKey, sessionId, workspaceDir, messageProvider. + // senderId/channelId/input are NOT available. Use event.prompt (user message incl. untrusted metadata). + const channel = (ctx.messageProvider || "").toLowerCase(); + if (live.discordOnly !== false && channel !== "discord") return; + + const prompt = ((event as Record).prompt as string) || ""; + const conv = extractUntrustedConversationInfo(prompt) || {}; + const senderId = + (typeof conv.sender_id === "string" && conv.sender_id) || + (typeof conv.sender === "string" && conv.sender) || + undefined; + const channelId = + (typeof conv.channel_id === "string" && conv.channel_id) || + (typeof (conv as Record).conversation_label === "string" + ? undefined + : undefined); + let rec = sessionDecision.get(key); if (!rec || Date.now() - rec.createdAt > DECISION_TTL_MS) { if (rec) sessionDecision.delete(key); - const c = (ctx || {}) as Record; - const derived = deriveDecisionInputFromAgentCtx(c); const decision = evaluateDecision({ config: live, - channel: derived.channel, - channelId: derived.channelId, + channel, + channelId, channelPolicies: policyState.channelPolicies, - senderId: derived.senderId, - content: derived.content, + senderId, + content: prompt, }); rec = { decision, createdAt: Date.now() }; sessionDecision.set(key, rec); pruneDecisionMap(); - if (shouldDebugLog(live, derived.channelId ?? ctx.channelId)) { + if (shouldDebugLog(live, channelId)) { api.logger.info( - `whispergate: debug before_model_resolve recompute session=${key} decision=${decision.reason} ` + + `whispergate: debug before_model_resolve recompute session=${key} senderId=${senderId} decision=${decision.reason} ` + `shouldNoReply=${decision.shouldUseNoReply} shouldInject=${decision.shouldInjectEndMarkerPrompt}`, ); } @@ -399,7 +414,7 @@ export default { }; }); - api.on("before_prompt_build", async (_event, ctx) => { + api.on("before_prompt_build", async (event, ctx) => { const key = ctx.sessionKey; if (!key) return; @@ -409,20 +424,31 @@ export default { let rec = sessionDecision.get(key); if (!rec || Date.now() - rec.createdAt > DECISION_TTL_MS) { if (rec) sessionDecision.delete(key); - const c = (ctx || {}) as Record; - const derived = deriveDecisionInputFromAgentCtx(c); + + // before_prompt_build has event.prompt and event.messages available. + const channel = (ctx.messageProvider || "").toLowerCase(); + const prompt = ((event as Record).prompt as string) || ""; + const conv = extractUntrustedConversationInfo(prompt) || {}; + const senderId = + (typeof conv.sender_id === "string" && conv.sender_id) || + (typeof conv.sender === "string" && conv.sender) || + undefined; + const channelId = + (typeof conv.channel_id === "string" && conv.channel_id) || + undefined; + const decision = evaluateDecision({ config: live, - channel: derived.channel, - channelId: derived.channelId, + channel, + channelId, channelPolicies: policyState.channelPolicies, - senderId: derived.senderId, - content: derived.content, + senderId, + content: prompt, }); rec = { decision, createdAt: Date.now() }; - if (shouldDebugLog(live, derived.channelId ?? ctx.channelId)) { + if (shouldDebugLog(live, channelId)) { api.logger.info( - `whispergate: debug before_prompt_build recompute session=${key} decision=${decision.reason} ` + + `whispergate: debug before_prompt_build recompute session=${key} senderId=${senderId} decision=${decision.reason} ` + `shouldNoReply=${decision.shouldUseNoReply} shouldInject=${decision.shouldInjectEndMarkerPrompt}`, ); } @@ -430,7 +456,7 @@ export default { sessionDecision.delete(key); if (!rec.decision.shouldInjectEndMarkerPrompt) { - if (shouldDebugLog(live, ctx.channelId)) { + if (shouldDebugLog(live, undefined)) { api.logger.info( `whispergate: debug before_prompt_build session=${key} inject=false reason=${rec.decision.reason}`, );