From f03d32e15f13abecec4495535eec078367f91596 Mon Sep 17 00:00:00 2001 From: zhi Date: Sat, 28 Feb 2026 19:27:39 +0000 Subject: [PATCH] fix: add turn check debug logs + one-time prompt injection 1. Turn check improvements: - Add debug logs for ctx.agentId, resolved accountId, turnOrder length - Fallback to ctx.accountId if resolveAccountId fails - Add resolveDiscordUserId debug logs for handoff troubleshooting 2. One-time prompt injection: - Add sessionInjected Set to track injected sessions - Use prependContext (not systemPrompt) but only inject once per session - Skip subsequent injections with debug log --- plugin/index.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/plugin/index.ts b/plugin/index.ts index 0959331..8bbb6fb 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -24,6 +24,7 @@ type DebugConfig = { }; const sessionDecision = new Map(); +const sessionInjected = new Set(); // Track which sessions have already injected the end marker const MAX_SESSION_DECISIONS = 2000; const DECISION_TTL_MS = 5 * 60 * 1000; function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean): string { @@ -261,8 +262,20 @@ function resolveDiscordUserId(api: OpenClawPluginApi, accountId: string): string const discord = (channels.discord as Record) || {}; const accounts = (discord.accounts as Record>) || {}; const acct = accounts[accountId]; - if (!acct?.token || typeof acct.token !== "string") return undefined; - return userIdFromToken(acct.token); + + if (!acct?.token || typeof acct.token !== "string") { + api.logger.warn(`whispergate: resolveDiscordUserId failed for accountId=${accountId}: no token found in config`); + return undefined; + } + + const userId = userIdFromToken(acct.token); + if (!userId) { + api.logger.warn(`whispergate: resolveDiscordUserId failed for accountId=${accountId}: could not parse userId from token`); + return undefined; + } + + api.logger.info(`whispergate: resolveDiscordUserId success accountId=${accountId} userId=${userId}`); + return userId; } /** Get the moderator bot's Discord user ID from its token */ @@ -611,7 +624,27 @@ export default { // This ensures only the current speaker can respond even for human messages. if (derived.channelId) { ensureTurnOrder(api, derived.channelId); - const accountId = resolveAccountId(api, ctx.agentId || ""); + + // Try resolveAccountId first, fall back to ctx.accountId if not found + let accountId = resolveAccountId(api, ctx.agentId || ""); + + // Debug log for turn check + if (shouldDebugLog(live, derived.channelId)) { + const turnDebug = getTurnDebugInfo(derived.channelId); + api.logger.info( + `whispergate: turn check preflight agentId=${ctx.agentId ?? "undefined"} ` + + `resolvedAccountId=${accountId ?? "undefined"} ` + + `ctxAccountId=${ctx.accountId ?? "undefined"} ` + + `turnOrderLen=${turnDebug.turnOrder?.length ?? 0} ` + + `currentSpeaker=${turnDebug.currentSpeaker ?? "null"}`, + ); + } + + // Fallback to ctx.accountId if resolveAccountId failed + if (!accountId && ctx.accountId) { + accountId = String(ctx.accountId); + } + if (accountId) { const turnCheck = checkTurn(derived.channelId, accountId); if (!turnCheck.allowed) { @@ -703,6 +736,17 @@ export default { } sessionDecision.delete(key); + + // Only inject once per session (one-time injection) + if (sessionInjected.has(key)) { + if (shouldDebugLog(live, undefined)) { + api.logger.info( + `whispergate: debug before_prompt_build session=${key} inject skipped (already injected)`, + ); + } + return; + } + if (!rec.decision.shouldInjectEndMarkerPrompt) { if (shouldDebugLog(live, undefined)) { api.logger.info( @@ -726,8 +770,11 @@ export default { if (idStr) identity = idStr + "\n\n"; } - api.logger.info(`whispergate: set system prompt for session=${key}, reason=${rec.decision.reason} isGroupChat=${isGroupChat}`); - return { systemPrompt: identity + instruction }; + // Mark session as injected (one-time injection) + sessionInjected.add(key); + + api.logger.info(`whispergate: one-time inject end marker for session=${key}, reason=${rec.decision.reason} isGroupChat=${isGroupChat}`); + return { prependContext: identity + instruction }; }); // Register slash commands for Discord