fix: guard WeChat page follow-up context

This commit is contained in:
john
2026-07-28 11:53:00 +08:00
parent 9b68dc6b77
commit 31cfdf7a94
3 changed files with 278 additions and 34 deletions
+114 -33
View File
@@ -974,6 +974,30 @@ export function shouldForceNewWechatAgentSession(wechatIntent, resetCandidate) {
);
}
const WECHAT_IMMEDIATE_CONTEXT_PAGE_PATTERN =
/^(?:(?:请|麻烦|可以|能不能)\s*)?(?:(?:帮我|帮忙|给我)\s*)?(?:(?:把\s*)?(?:这个|刚才(?:的)?(?:诗|文章|内容)?|上面(?:的)?|前面(?:的)?|上一条|它|这首(?:诗)?|这篇(?:文章)?|这段(?:内容)?)\s*)?(?:继续\s*)?(?:做|弄|改|转|写|排版|生成|制作)(?:成|为)?\s*(?:一个|个)?\s*(?:h5|html)?\s*(?:页面|网页)(?:吧|呀|呢|一下)?[。.!\s]*$/iu;
export function isWechatImmediateContextFollowup(wechatIntent, text) {
if (wechatIntent?.kind !== 'page.generate') return false;
const normalized = String(text ?? '').trim();
return Boolean(normalized && WECHAT_IMMEDIATE_CONTEXT_PAGE_PATTERN.test(normalized));
}
export function shouldRotateUnlinkedWechatRoute({
routeUpdatedAt,
wechatMessageCount,
snapshotMessageCount,
now = Date.now(),
} = {}) {
const updatedAt = Number(routeUpdatedAt ?? 0);
return (
updatedAt > 0 &&
Number(now) - updatedAt > 60_000 &&
Number(wechatMessageCount) === 0 &&
Number(snapshotMessageCount ?? 0) > 0
);
}
export function isWechatPageDataTask(text) {
return isPageDataIntent(text) || isPageDataDevIntent(text);
}
@@ -1978,42 +2002,56 @@ export function createWechatMpService({
config.sessionIdleRotateMs > 0 &&
routeUpdatedAt > 0 &&
now - routeUpdatedAt > config.sessionIdleRotateMs;
let routeWechatMessageCount = null;
let routeSnapshotMessageCount = null;
let routeMessageCount = 0;
if (config.sessionMessageRotateCount > 0) {
const counts = [];
if (typeof userAuth.countWechatAgentSessionMessages === 'function') {
counts.push(
userAuth
.countWechatAgentSessionMessages({
appId: config.appId,
openid,
agentSessionId: existingRoute.agentSessionId,
})
.catch((err) => {
logger.warn?.('WeChat MP route message count lookup failed:', err);
return 0;
}),
);
}
if (typeof userAuth.getWechatAgentSessionSnapshotMessageCount === 'function') {
counts.push(
userAuth
.getWechatAgentSessionSnapshotMessageCount(existingRoute.agentSessionId)
.catch((err) => {
logger.warn?.('WeChat MP snapshot message count lookup failed:', err);
return 0;
}),
);
}
if (counts.length > 0) {
const resolved = await Promise.all(counts);
routeMessageCount = Math.max(0, ...resolved.map((value) => Number(value) || 0));
}
[routeWechatMessageCount, routeSnapshotMessageCount] = await Promise.all([
typeof userAuth.countWechatAgentSessionMessages === 'function'
? userAuth
.countWechatAgentSessionMessages({
appId: config.appId,
openid,
agentSessionId: existingRoute.agentSessionId,
})
.then((value) => Number(value) || 0)
.catch((err) => {
logger.warn?.('WeChat MP route message count lookup failed:', err);
return null;
})
: null,
typeof userAuth.getWechatAgentSessionSnapshotMessageCount === 'function'
? userAuth
.getWechatAgentSessionSnapshotMessageCount(existingRoute.agentSessionId)
.then((value) => Number(value) || 0)
.catch((err) => {
logger.warn?.('WeChat MP snapshot message count lookup failed:', err);
return null;
})
: null,
]);
routeMessageCount = Math.max(
0,
Number(routeWechatMessageCount) || 0,
Number(routeSnapshotMessageCount) || 0,
);
}
const routeIsTooLong =
config.sessionMessageRotateCount > 0 &&
Number(routeMessageCount ?? 0) >= config.sessionMessageRotateCount;
if (routeIsIdle || routeIsTooLong) {
const routeHasUnlinkedConversation = shouldRotateUnlinkedWechatRoute({
routeUpdatedAt,
wechatMessageCount: routeWechatMessageCount,
snapshotMessageCount: routeSnapshotMessageCount,
now,
});
if (routeIsIdle || routeIsTooLong || routeHasUnlinkedConversation) {
if (routeHasUnlinkedConversation) {
logger.warn?.('WeChat MP orphaned route rotated before reuse:', {
agentSessionId: existingRoute.agentSessionId,
snapshotMessageCount: routeSnapshotMessageCount,
});
}
await userAuth.clearWechatAgentRoute(config.appId, openid);
rememberedWechatContexts.delete(existingRoute.agentSessionId);
} else {
@@ -2271,7 +2309,12 @@ export function createWechatMpService({
}
};
const prepareWechatAgentUserMessage = async ({ userId, sessionId, userMessage }) => {
const prepareWechatAgentUserMessage = async ({
userId,
sessionId,
userMessage,
preserveAgentPrompt = false,
}) => {
if (
!chatIntentRouter?.resolveAgentMemoryContext
|| !chatIntentRouter?.applyAgentOrchestration
@@ -2296,8 +2339,17 @@ export function createWechatMpService({
) {
return userMessage;
}
return chatIntentRouter.applyAgentOrchestration(
userMessage,
const orchestrationMessage = preserveAgentPrompt
? {
...userMessage,
metadata: {
...(userMessage?.metadata ?? {}),
displayText: messageVisibleText(userMessage),
},
}
: userMessage;
const prepared = chatIntentRouter.applyAgentOrchestration(
orchestrationMessage,
{
route: 'agent_orchestration',
reason: '微信服务号消息由 Agent 处理',
@@ -2305,6 +2357,14 @@ export function createWechatMpService({
},
{ memoryContext },
);
if (!preserveAgentPrompt) return prepared;
return {
...prepared,
metadata: {
...(prepared?.metadata ?? {}),
displayText: displayText || undefined,
},
};
} catch (err) {
logger.warn?.(
'WeChat MP agent memory resolve skipped:',
@@ -2371,6 +2431,8 @@ export function createWechatMpService({
// inspect/retry unrelated historical pages from that session.
const isPageDataRequest = isWechatPageDataTask(resetCandidate);
const htmlArtifactDeliveryExpected = shouldDeliverWechatHtmlArtifacts(wechatIntent, intent);
const immediateContextFollowup =
isWechatImmediateContextFollowup(wechatIntent, resetCandidate);
const forceNew = shouldForceNewWechatAgentSession(wechatIntent, resetCandidate);
let route = await ensureWechatAgentSession({
userId: user.userId,
@@ -2417,6 +2479,7 @@ export function createWechatMpService({
? buildPageGenerateAgentPrompt(intent, {
wantsDocx: wechatIntent.wantsDocx,
imagePolicy,
preferImmediateContext: immediateContextFollowup,
})
: buildWechatAgentPrompt(intent, { imagePolicy });
const reply = await executeSessionReply(
@@ -2434,6 +2497,7 @@ export function createWechatMpService({
userId: user.userId,
sessionId,
userMessage,
preserveAgentPrompt: immediateContextFollowup,
}),
submitReply: submitSessionReply
? ({ requestId: replyRequestId, userMessage }) =>
@@ -2657,6 +2721,21 @@ export function createWechatMpService({
sessionId
&& (isRecoverableWechatAgentSessionError(message) || isWechatAgentApiErrorText(message));
if (mayBeStaleSession) {
if (immediateContextFollowup) {
await userAuth.clearWechatAgentRoute(config.appId, inbound.fromUserName).catch((clearErr) => {
logger.warn?.('WeChat MP contextual follow-up route clear failed:', clearErr);
});
const text =
'我没能可靠确认“做成页面”指的是哪段内容,已经停止沿用旧主题。请把要做成页面的主题或原文再发一次。';
try {
await sendCustomerServiceText(inbound.fromUserName, text, user);
} catch (sendErr) {
logger.error?.('WeChat MP contextual follow-up notice failed:', sendErr);
}
const contextualError = markWechatUserNotified(new Error(text));
contextualError.wechatAgentSessionId = sessionId;
throw contextualError;
}
route = await ensureWechatAgentSession({
userId: user.userId,
openid: inbound.fromUserName,
@@ -2673,6 +2752,7 @@ export function createWechatMpService({
? buildPageGenerateAgentPrompt(intent, {
wantsDocx: wechatIntent.wantsDocx,
imagePolicy,
preferImmediateContext: immediateContextFollowup,
})
: buildWechatAgentPrompt(intent, { imagePolicy });
const reply = await executeSessionReply(
@@ -2690,6 +2770,7 @@ export function createWechatMpService({
userId: user.userId,
sessionId,
userMessage,
preserveAgentPrompt: immediateContextFollowup,
}),
submitReply: submitSessionReply
? ({ requestId: replyRequestId, userMessage }) =>