feat(debug): add whispergate hook diagnostics with channel-scoped debug logs
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
"noReplyModel": "no-reply",
|
||||
"enableDiscordControlTool": true,
|
||||
"enableWhispergatePolicyTool": true,
|
||||
"enableDebugLogs": false,
|
||||
"debugLogChannelIds": [],
|
||||
"discordControlApiBaseUrl": "http://127.0.0.1:8790",
|
||||
"discordControlApiToken": "<DISCORD_CONTROL_AUTH_TOKEN>",
|
||||
"discordControlCallerId": "agent-main"
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -15,6 +15,11 @@ type PolicyState = {
|
||||
channelPolicies: Record<string, ChannelPolicy>;
|
||||
};
|
||||
|
||||
type DebugConfig = {
|
||||
enableDebugLogs?: boolean;
|
||||
debugLogChannelIds?: string[];
|
||||
};
|
||||
|
||||
const sessionDecision = new Map<string, DecisionRecord>();
|
||||
const MAX_SESSION_DECISIONS = 2000;
|
||||
const DECISION_TTL_MS = 5 * 60 * 1000;
|
||||
@@ -119,6 +124,39 @@ function pickDefined(input: Record<string, unknown>) {
|
||||
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<string, unknown>, event: Record<string, unknown>) {
|
||||
const meta = ((ctx.metadata || event.metadata || {}) as Record<string, unknown>) || {};
|
||||
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 };
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user