fix: channelId extraction, sender identification, and per-channel turn order #9
@@ -26,6 +26,7 @@ type DebugConfig = {
|
|||||||
const sessionDecision = new Map<string, DecisionRecord>();
|
const sessionDecision = new Map<string, DecisionRecord>();
|
||||||
const sessionAllowed = new Map<string, boolean>(); // Track if session was allowed to speak (true) or forced no-reply (false)
|
const sessionAllowed = new Map<string, boolean>(); // Track if session was allowed to speak (true) or forced no-reply (false)
|
||||||
const sessionInjected = new Set<string>(); // Track which sessions have already injected the end marker
|
const sessionInjected = new Set<string>(); // Track which sessions have already injected the end marker
|
||||||
|
const sessionChannelId = new Map<string, string>(); // Track sessionKey -> channelId mapping
|
||||||
const MAX_SESSION_DECISIONS = 2000;
|
const MAX_SESSION_DECISIONS = 2000;
|
||||||
const DECISION_TTL_MS = 5 * 60 * 1000;
|
const DECISION_TTL_MS = 5 * 60 * 1000;
|
||||||
function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean): string {
|
function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean): string {
|
||||||
@@ -636,6 +637,10 @@ export default {
|
|||||||
}
|
}
|
||||||
// Allowed to speak - record this session as allowed
|
// Allowed to speak - record this session as allowed
|
||||||
sessionAllowed.set(key, true);
|
sessionAllowed.set(key, true);
|
||||||
|
// Also save channelId for this session
|
||||||
|
if (derived.channelId) {
|
||||||
|
sessionChannelId.set(key, derived.channelId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,33 +813,74 @@ export default {
|
|||||||
// NOTE: This hook is synchronous, do not use async/await
|
// NOTE: This hook is synchronous, do not use async/await
|
||||||
api.on("before_message_write", (event, ctx) => {
|
api.on("before_message_write", (event, ctx) => {
|
||||||
try {
|
try {
|
||||||
const key = ctx.sessionKey;
|
// Debug: print all available keys in event and ctx
|
||||||
const channelId = ctx.channelId as string | undefined;
|
api.logger.info(
|
||||||
const accountId = ctx.accountId as string | undefined;
|
`whispergate: DEBUG before_message_write eventKeys=${JSON.stringify(Object.keys(event ?? {}))} ctxKeys=${JSON.stringify(Object.keys(ctx ?? {}))}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try multiple sources for channelId and accountId
|
||||||
|
const deliveryContext = (ctx as Record<string, unknown>)?.deliveryContext as Record<string, unknown> | undefined;
|
||||||
|
let key = ctx.sessionKey;
|
||||||
|
let channelId = ctx.channelId as string | undefined;
|
||||||
|
let accountId = ctx.accountId as string | undefined;
|
||||||
|
let content = (event.content as string) || "";
|
||||||
|
|
||||||
|
// Fallback: get channelId from deliveryContext.to
|
||||||
|
if (!channelId && deliveryContext?.to) {
|
||||||
|
const toStr = String(deliveryContext.to);
|
||||||
|
channelId = toStr.startsWith("channel:") ? toStr.replace("channel:", "") : toStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: get accountId from deliveryContext.accountId
|
||||||
|
if (!accountId && deliveryContext?.accountId) {
|
||||||
|
accountId = String(deliveryContext.accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: get content from event.message.content
|
||||||
|
if (!content && (event as Record<string, unknown>).message) {
|
||||||
|
const msg = (event as Record<string, unknown>).message as Record<string, unknown>;
|
||||||
|
content = String(msg.content ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always log for debugging - show all available info
|
||||||
|
api.logger.info(
|
||||||
|
`whispergate: DEBUG before_message_write session=${key ?? "undefined"} channel=${channelId ?? "undefined"} accountId=${accountId ?? "undefined"} contentType=${typeof content} content=${String(content).slice(0, 200)}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!key || !channelId || !accountId) return;
|
if (!key || !channelId || !accountId) return;
|
||||||
|
|
||||||
const live = getLivePluginConfig(api, baseConfig as WhisperGateConfig) as WhisperGateConfig & DebugConfig;
|
const live = getLivePluginConfig(api, baseConfig as WhisperGateConfig) as WhisperGateConfig & DebugConfig;
|
||||||
|
|
||||||
// Get the agent's output content
|
|
||||||
const content = (event.content as string) || "";
|
|
||||||
const trimmed = content.trim();
|
const trimmed = content.trim();
|
||||||
const isNoReply = /^NO_REPLY$/i.test(trimmed);
|
const isNoReply = /^NO_REPLY$/i.test(trimmed);
|
||||||
|
|
||||||
if (!isNoReply) return;
|
// Log turn state for debugging
|
||||||
|
const turnDebug = getTurnDebugInfo(channelId);
|
||||||
|
api.logger.info(
|
||||||
|
`whispergate: DEBUG turn state channel=${channelId} turnOrder=${JSON.stringify(turnDebug.turnOrder)} currentSpeaker=${turnDebug.currentSpeaker} noRepliedThisCycle=${JSON.stringify([...turnDebug.noRepliedThisCycle])}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isNoReply) {
|
||||||
|
api.logger.info(
|
||||||
|
`whispergate: before_message_write content is not NO_REPLY, skipping channel=${channelId}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this session was forced no-reply or allowed to speak
|
// Check if this session was forced no-reply or allowed to speak
|
||||||
const wasAllowed = sessionAllowed.get(key);
|
const wasAllowed = sessionAllowed.get(key);
|
||||||
|
api.logger.info(
|
||||||
|
`whispergate: DEBUG NO_REPLY detected session=${key} wasAllowed=${wasAllowed}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (wasAllowed === undefined) return; // No record, skip
|
if (wasAllowed === undefined) return; // No record, skip
|
||||||
|
|
||||||
if (wasAllowed === false) {
|
if (wasAllowed === false) {
|
||||||
// Forced no-reply - do not advance turn
|
// Forced no-reply - do not advance turn
|
||||||
sessionAllowed.delete(key);
|
sessionAllowed.delete(key);
|
||||||
if (shouldDebugLog(live, channelId)) {
|
|
||||||
api.logger.info(
|
api.logger.info(
|
||||||
`whispergate: before_message_write forced no-reply session=${key} channel=${channelId} - not advancing turn`,
|
`whispergate: before_message_write forced no-reply session=${key} channel=${channelId} - not advancing turn`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,11 +890,9 @@ export default {
|
|||||||
|
|
||||||
sessionAllowed.delete(key);
|
sessionAllowed.delete(key);
|
||||||
|
|
||||||
if (shouldDebugLog(live, channelId)) {
|
|
||||||
api.logger.info(
|
api.logger.info(
|
||||||
`whispergate: before_message_write real no-reply session=${key} channel=${channelId} nextSpeaker=${nextSpeaker ?? "dormant"}`,
|
`whispergate: before_message_write real no-reply session=${key} channel=${channelId} nextSpeaker=${nextSpeaker ?? "dormant"}`,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// If all agents NO_REPLY'd (dormant), don't trigger handoff
|
// If all agents NO_REPLY'd (dormant), don't trigger handoff
|
||||||
if (!nextSpeaker) {
|
if (!nextSpeaker) {
|
||||||
@@ -880,9 +924,19 @@ export default {
|
|||||||
// Turn advance: when an agent sends a message, check if it signals end of turn
|
// Turn advance: when an agent sends a message, check if it signals end of turn
|
||||||
api.on("message_sent", async (event, ctx) => {
|
api.on("message_sent", async (event, ctx) => {
|
||||||
try {
|
try {
|
||||||
const channelId = ctx.channelId;
|
const key = ctx.sessionKey;
|
||||||
const accountId = ctx.accountId;
|
// Try ctx.channelId first, fallback to sessionChannelId mapping
|
||||||
const content = event.content || "";
|
let channelId = ctx.channelId as string | undefined;
|
||||||
|
if (!channelId && key) {
|
||||||
|
channelId = sessionChannelId.get(key);
|
||||||
|
}
|
||||||
|
const accountId = ctx.accountId as string | undefined;
|
||||||
|
const content = (event.content as string) || "";
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
api.logger.info(
|
||||||
|
`whispergate: DEBUG message_sent session=${key ?? "undefined"} channelId=${channelId ?? "undefined"} accountId=${accountId ?? "undefined"} content=${content.slice(0, 100)}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!channelId || !accountId) return;
|
if (!channelId || !accountId) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user