From 51149dd6a03d39af18f1266f8063d60c83369918 Mon Sep 17 00:00:00 2001 From: orion Date: Thu, 26 Feb 2026 01:58:01 +0000 Subject: [PATCH] feat(debug): add whispergate hook diagnostics with channel-scoped debug logs --- docs/CONFIG.example.json | 2 ++ plugin/README.md | 7 ++++ plugin/index.ts | 72 +++++++++++++++++++++++++++++++++++-- plugin/openclaw.plugin.json | 4 ++- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/CONFIG.example.json b/docs/CONFIG.example.json index 173fcfd..0c887fb 100644 --- a/docs/CONFIG.example.json +++ b/docs/CONFIG.example.json @@ -18,6 +18,8 @@ "noReplyModel": "no-reply", "enableDiscordControlTool": true, "enableWhispergatePolicyTool": true, + "enableDebugLogs": false, + "debugLogChannelIds": [], "discordControlApiBaseUrl": "http://127.0.0.1:8790", "discordControlApiToken": "", "discordControlCallerId": "agent-main" diff --git a/plugin/README.md b/plugin/README.md index 38f4f2c..db01bbf 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -42,6 +42,8 @@ Unified optional tool: - `discordControlApiBaseUrl` (default `http://127.0.0.1:8790`) - `discordControlApiToken` - `discordControlCallerId` +- `enableDebugLogs` (default false) +- `debugLogChannelIds` (default [], empty = all channels when debug enabled) Per-channel policy file example: `docs/channel-policies.example.json`. @@ -61,3 +63,8 @@ To use it, add tool allowlist entry for either: Supported actions: - Discord: `channel-private-create`, `channel-private-update`, `member-list` - Policy: `policy-get`, `policy-set-channel`, `policy-delete-channel` + +Debug logging: +- set `enableDebugLogs: true` to emit detailed hook diagnostics +- optionally set `debugLogChannelIds` to only log selected channel IDs +- logs include key ctx fields + decision status at `message_received`, `before_model_resolve`, `before_prompt_build` diff --git a/plugin/index.ts b/plugin/index.ts index 8f6307d..bb7c30e 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -15,6 +15,11 @@ type PolicyState = { channelPolicies: Record; }; +type DebugConfig = { + enableDebugLogs?: boolean; + debugLogChannelIds?: string[]; +}; + const sessionDecision = new Map(); const MAX_SESSION_DECISIONS = 2000; const DECISION_TTL_MS = 5 * 60 * 1000; @@ -119,6 +124,39 @@ function pickDefined(input: Record) { return out; } +function shouldDebugLog(cfg: DebugConfig, channelId?: string): boolean { + if (!cfg.enableDebugLogs) return false; + const allow = Array.isArray(cfg.debugLogChannelIds) ? cfg.debugLogChannelIds : []; + if (allow.length === 0) return true; + if (!channelId) return false; + return allow.includes(channelId); +} + +function debugCtxSummary(ctx: Record, event: Record) { + const meta = ((ctx.metadata || event.metadata || {}) as Record) || {}; + return { + sessionKey: typeof ctx.sessionKey === "string" ? ctx.sessionKey : undefined, + commandSource: typeof ctx.commandSource === "string" ? ctx.commandSource : undefined, + messageProvider: typeof ctx.messageProvider === "string" ? ctx.messageProvider : undefined, + channel: typeof ctx.channel === "string" ? ctx.channel : undefined, + channelId: typeof ctx.channelId === "string" ? ctx.channelId : undefined, + senderId: typeof ctx.senderId === "string" ? ctx.senderId : undefined, + from: typeof ctx.from === "string" ? ctx.from : undefined, + metaSenderId: + typeof meta.senderId === "string" + ? meta.senderId + : typeof meta.sender_id === "string" + ? meta.sender_id + : undefined, + metaUserId: + typeof meta.userId === "string" + ? meta.userId + : typeof meta.user_id === "string" + ? meta.user_id + : undefined, + }; +} + export default { id: "whispergate", name: "WhisperGate", @@ -290,6 +328,14 @@ export default { api.logger.debug?.( `whispergate: session=${sessionKey} sender=${senderId ?? "unknown"} channel=${channel || "unknown"} decision=${decision.reason}`, ); + + if (shouldDebugLog(live as DebugConfig, channelId)) { + const summary = debugCtxSummary(c, e); + api.logger.info( + `whispergate: debug message_received session=${sessionKey} decision=${decision.reason} ` + + `shouldNoReply=${decision.shouldUseNoReply} shouldInject=${decision.shouldInjectEndMarkerPrompt} ctx=${JSON.stringify(summary)}`, + ); + } } catch (err) { api.logger.warn(`whispergate: message hook failed: ${String(err)}`); } @@ -300,7 +346,13 @@ export default { if (!key) return; const rec = sessionDecision.get(key); - if (!rec) return; + if (!rec) { + const liveMiss = getLivePluginConfig(api, baseConfig as WhisperGateConfig) as WhisperGateConfig & DebugConfig; + if (shouldDebugLog(liveMiss, ctx.channelId)) { + api.logger.info(`whispergate: debug before_model_resolve session=${key} decision=missing`); + } + return; + } if (Date.now() - rec.createdAt > DECISION_TTL_MS) { sessionDecision.delete(key); return; @@ -324,7 +376,13 @@ export default { const key = ctx.sessionKey; if (!key) return; const rec = sessionDecision.get(key); - if (!rec) return; + if (!rec) { + const liveMiss = getLivePluginConfig(api, baseConfig as WhisperGateConfig) as WhisperGateConfig & DebugConfig; + if (shouldDebugLog(liveMiss, ctx.channelId)) { + api.logger.info(`whispergate: debug before_prompt_build session=${key} decision=missing`); + } + return; + } if (Date.now() - rec.createdAt > DECISION_TTL_MS) { sessionDecision.delete(key); @@ -332,7 +390,15 @@ export default { } sessionDecision.delete(key); - if (!rec.decision.shouldInjectEndMarkerPrompt) return; + if (!rec.decision.shouldInjectEndMarkerPrompt) { + const liveSkip = getLivePluginConfig(api, baseConfig as WhisperGateConfig) as WhisperGateConfig & DebugConfig; + if (shouldDebugLog(liveSkip, ctx.channelId)) { + api.logger.info( + `whispergate: debug before_prompt_build session=${key} inject=false reason=${rec.decision.reason}`, + ); + } + return; + } api.logger.info(`whispergate: prepend end marker instruction for session=${key}, reason=${rec.decision.reason}`); return { prependContext: END_MARKER_INSTRUCTION }; diff --git a/plugin/openclaw.plugin.json b/plugin/openclaw.plugin.json index 2872ff9..d10f3e5 100644 --- a/plugin/openclaw.plugin.json +++ b/plugin/openclaw.plugin.json @@ -22,7 +22,9 @@ "enableWhispergatePolicyTool": { "type": "boolean", "default": true }, "discordControlApiBaseUrl": { "type": "string", "default": "http://127.0.0.1:8790" }, "discordControlApiToken": { "type": "string" }, - "discordControlCallerId": { "type": "string" } + "discordControlCallerId": { "type": "string" }, + "enableDebugLogs": { "type": "boolean", "default": false }, + "debugLogChannelIds": { "type": "array", "items": { "type": "string" }, "default": [] } }, "required": ["noReplyProvider", "noReplyModel"] }