fix(wechat): guard bare page-continuation follow-ups from session loss and memory drift
Memind CI / Test, build, and release guards (push) Failing after 3s
Memind CI / Test, build, and release guards (push) Failing after 3s
Prevent chat-to-page flows like writing a poem then saying "做成页面吧" from opening a fresh session without carried content, failing with a vague error, or letting Memory page-template preferences replace the immediate topic. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+110
-3
@@ -44,6 +44,13 @@ import {
|
||||
isWechatSessionPageContinuation,
|
||||
isWechatContentEditFollowup,
|
||||
} from './wechat/intent/page-continuation.mjs';
|
||||
import {
|
||||
extractLastSubstantiveAssistantFromConversation,
|
||||
filterMemoryForImmediatePageContext,
|
||||
isWechatPageContinuationRepairableError,
|
||||
shouldFailFastWechatPageContinuation,
|
||||
WECHAT_PAGE_CONTINUATION_MISSING_SOURCE_TEXT,
|
||||
} from './wechat/intent/page-continuation-context.mjs';
|
||||
import { resolvePageGenerateOutcome } from './wechat/handlers/page-generate.mjs';
|
||||
import {
|
||||
MEMORY_V2_PRODUCT_EVENT_TYPES,
|
||||
@@ -231,6 +238,18 @@ function messageVisibleText(message) {
|
||||
.join('');
|
||||
}
|
||||
|
||||
async function fetchLastSubstantiveAssistantFromSession(fetchForSession, sessionId) {
|
||||
if (!sessionId || typeof fetchForSession !== 'function') return '';
|
||||
try {
|
||||
const payload = await readJsonResponse(
|
||||
await fetchForSession(sessionId, `/sessions/${encodeURIComponent(sessionId)}`),
|
||||
);
|
||||
return extractLastSubstantiveAssistantFromConversation(payload?.conversation ?? []);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function pushMessage(messages, incoming) {
|
||||
const last = messages[messages.length - 1];
|
||||
if (last?.id && incoming?.id && last.id === incoming.id) {
|
||||
@@ -2118,6 +2137,7 @@ export function createWechatMpService({
|
||||
await userAuth.getAgentSessionPolicy(userId);
|
||||
const publishLayout = await userAuth.getUserPublishLayout(userId);
|
||||
const addressName = resolveWechatAddressName(userContext);
|
||||
let carriedSessionContentForNewSession = '';
|
||||
const existingRoute = await userAuth.getWechatAgentRoute(config.appId, openid);
|
||||
if (existingRoute?.agentSessionId) {
|
||||
const now = Date.now();
|
||||
@@ -2223,7 +2243,25 @@ export function createWechatMpService({
|
||||
logger.warn?.('WeChat MP route touch failed:', err);
|
||||
});
|
||||
}
|
||||
return { sessionId: existingRoute.agentSessionId, isNewSession: false };
|
||||
return {
|
||||
sessionId: existingRoute.agentSessionId,
|
||||
isNewSession: false,
|
||||
carriedSessionContent: '',
|
||||
};
|
||||
}
|
||||
let carriedSessionContent = '';
|
||||
if (retainUnlinkedRoute) {
|
||||
carriedSessionContent = await fetchLastSubstantiveAssistantFromSession(
|
||||
fetchForSession,
|
||||
existingRoute.agentSessionId,
|
||||
);
|
||||
if (carriedSessionContent) {
|
||||
logger.warn?.('WeChat MP retaining prior assistant content after route tool mismatch:', {
|
||||
agentSessionId: existingRoute.agentSessionId,
|
||||
contentLength: carriedSessionContent.length,
|
||||
});
|
||||
carriedSessionContentForNewSession = carriedSessionContent;
|
||||
}
|
||||
}
|
||||
await userAuth.clearWechatAgentRoute(config.appId, openid);
|
||||
rememberedWechatContexts.delete(existingRoute.agentSessionId);
|
||||
@@ -2304,7 +2342,11 @@ export function createWechatMpService({
|
||||
openid,
|
||||
agentSessionId: sessionId,
|
||||
});
|
||||
return { sessionId, isNewSession: true };
|
||||
return {
|
||||
sessionId,
|
||||
isNewSession: true,
|
||||
carriedSessionContent: carriedSessionContentForNewSession,
|
||||
};
|
||||
};
|
||||
|
||||
const refreshWechatSessionSnapshot = async (sessionId, userId) => {
|
||||
@@ -2452,12 +2494,13 @@ export function createWechatMpService({
|
||||
?? messageVisibleText(userMessage),
|
||||
).trim();
|
||||
try {
|
||||
const memoryContext = await chatIntentRouter.resolveAgentMemoryContext({
|
||||
const memoryContextRaw = await chatIntentRouter.resolveAgentMemoryContext({
|
||||
userId,
|
||||
sessionId,
|
||||
text: displayText,
|
||||
forceDeepReasoning: false,
|
||||
});
|
||||
let memoryContext = memoryContextRaw;
|
||||
if (
|
||||
!memoryContext?.injectionEnabled
|
||||
|| !Array.isArray(memoryContext.memories)
|
||||
@@ -2465,6 +2508,15 @@ export function createWechatMpService({
|
||||
) {
|
||||
return userMessage;
|
||||
}
|
||||
if (preserveAgentPrompt) {
|
||||
memoryContext = {
|
||||
...memoryContext,
|
||||
memories: filterMemoryForImmediatePageContext(memoryContext.memories),
|
||||
};
|
||||
if (memoryContext.memories.length === 0) {
|
||||
return userMessage;
|
||||
}
|
||||
}
|
||||
const memoryCount = memoryContext.memories.length;
|
||||
if (mysqlPool?.query) {
|
||||
void recordMemoryV2ProductEvent(mysqlPool, {
|
||||
@@ -2597,6 +2649,7 @@ export function createWechatMpService({
|
||||
retainUnlinkedRoute: sessionPageContinuation,
|
||||
});
|
||||
let sessionId = route.sessionId;
|
||||
let carriedSessionContent = String(route.carriedSessionContent ?? '').trim();
|
||||
await ensureSessionProvider(sessionId);
|
||||
if (wechatIntent.kind === 'session.reset') {
|
||||
await sendCustomerServiceText(
|
||||
@@ -2607,6 +2660,34 @@ export function createWechatMpService({
|
||||
return { sessionId };
|
||||
}
|
||||
await rememberWechatUserContext(sessionId, user, { forceBootstrap: route.isNewSession });
|
||||
if (
|
||||
wechatIntent.kind === 'page.generate'
|
||||
&& sessionPageContinuation
|
||||
) {
|
||||
const sessionAssistantContent = carriedSessionContent
|
||||
|| await fetchLastSubstantiveAssistantFromSession(fetchForSession, sessionId);
|
||||
if (
|
||||
shouldFailFastWechatPageContinuation({
|
||||
sessionPageContinuation,
|
||||
userText: resetCandidate,
|
||||
carriedSessionContent,
|
||||
sessionAssistantContent,
|
||||
})
|
||||
) {
|
||||
const text = WECHAT_PAGE_CONTINUATION_MISSING_SOURCE_TEXT;
|
||||
try {
|
||||
await sendWechatFailureNotice(inbound.fromUserName, text, user, {
|
||||
sourceMsgId: intent.msgId,
|
||||
});
|
||||
} catch (sendErr) {
|
||||
logger.error?.('WeChat MP page continuation missing-source notice failed:', sendErr);
|
||||
}
|
||||
throw markWechatUserNotified(new Error(text));
|
||||
}
|
||||
if (!carriedSessionContent && sessionAssistantContent) {
|
||||
carriedSessionContent = sessionAssistantContent;
|
||||
}
|
||||
}
|
||||
let finished = false;
|
||||
let progressTimer = null;
|
||||
const skipProgressReply =
|
||||
@@ -2641,6 +2722,7 @@ export function createWechatMpService({
|
||||
wantsDocx: wechatIntent.wantsDocx,
|
||||
imagePolicy,
|
||||
preferImmediateContext: sessionPageContinuation,
|
||||
carriedSessionContent,
|
||||
})
|
||||
: buildWechatAgentPrompt(intent, {
|
||||
imagePolicy,
|
||||
@@ -2765,6 +2847,14 @@ export function createWechatMpService({
|
||||
},
|
||||
});
|
||||
if (pageOutcome.action === 'session_retry') {
|
||||
if (sessionPageContinuation && pageAttempt < maxImmediateContextPageAttempts) {
|
||||
logger.warn?.('WeChat MP immediate-context fake delivery repair retry:', {
|
||||
sessionId,
|
||||
userId: user.userId,
|
||||
pageAttempt,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new Error('stale_session_poisoned_completion');
|
||||
}
|
||||
if (pageOutcome.action === 'fail') {
|
||||
@@ -2987,6 +3077,22 @@ export function createWechatMpService({
|
||||
sessionId
|
||||
&& (isRecoverableWechatAgentSessionError(message) || isWechatAgentApiErrorText(message));
|
||||
if (mayBeStaleSession) {
|
||||
if (sessionPageContinuation && isWechatPageContinuationRepairableError(message)) {
|
||||
const text = buildPagePublishFailureText();
|
||||
await userAuth.clearWechatAgentRoute(config.appId, inbound.fromUserName).catch((clearErr) => {
|
||||
logger.warn?.('WeChat MP page continuation repair route clear failed:', clearErr);
|
||||
});
|
||||
try {
|
||||
await sendWechatFailureNotice(inbound.fromUserName, text, user, {
|
||||
sourceMsgId: intent.msgId,
|
||||
});
|
||||
} catch (sendErr) {
|
||||
logger.error?.('WeChat MP page continuation repair notice failed:', sendErr);
|
||||
}
|
||||
const repairError = markWechatUserNotified(new Error(text));
|
||||
repairError.wechatAgentSessionId = sessionId;
|
||||
throw repairError;
|
||||
}
|
||||
if (sessionPageContinuation) {
|
||||
await userAuth.clearWechatAgentRoute(config.appId, inbound.fromUserName).catch((clearErr) => {
|
||||
logger.warn?.('WeChat MP contextual follow-up route clear failed:', clearErr);
|
||||
@@ -3021,6 +3127,7 @@ export function createWechatMpService({
|
||||
wantsDocx: wechatIntent.wantsDocx,
|
||||
imagePolicy,
|
||||
preferImmediateContext: sessionPageContinuation,
|
||||
carriedSessionContent,
|
||||
})
|
||||
: buildWechatAgentPrompt(intent, {
|
||||
imagePolicy,
|
||||
|
||||
Reference in New Issue
Block a user