fix(wechat): rotate polluted image sessions before follow-up turns
Memind CI / Test, build, and release guards (push) Successful in 3m28s

After a successful image report flow, Goose may keep image_url parts that
DeepSeek rejects on later turns while PUT scrub returns 405. Detect polluted
sessions up front, rotate to a fresh agent route with carried assistant
context, and recover image URLs from recent media on historical retries.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-15 09:06:00 +08:00
parent fbd68a36e9
commit 9f7633be24
2 changed files with 224 additions and 6 deletions
+91 -6
View File
@@ -85,6 +85,7 @@ import { resolveMindSpaceUserPublishDir } from './mindspace-runtime-config.mjs';
import {
buildPageDataCollectFailureText,
} from './mindspace-page-data-finish-guard.mjs';
import { conversationHasImageUrlContent } from './chat-image-turn-scope.mjs';
export { buildWechatAgentPrompt };
@@ -1097,9 +1098,14 @@ export function isWechatHistoricalImageSessionError(message) {
export const WECHAT_IMAGE_STORED_ACK_TEXT =
'已收到图片。请直接告诉我接下来要做什么,例如:解读报告并生成页面。';
export function prepareWechatIntentForHistoricalImageRetry(intent) {
export function resolveWechatRecentMediaPublicUrl(recentMediaEntry) {
if (!recentMediaEntry?.items?.length) return '';
return String(recentMediaEntry.items.at(-1)?.media?.publicUrl ?? '').trim();
}
export function prepareWechatIntentForHistoricalImageRetry(intent, { fallbackImageUrl = '' } = {}) {
if (!intent || typeof intent !== 'object') return intent;
const imageUrl = String(intent.media?.publicUrl ?? '').trim();
const imageUrl = String(intent.media?.publicUrl ?? fallbackImageUrl ?? '').trim();
const agentText = String(intent.agentText ?? '').trim();
if (imageUrl && !agentText.includes(imageUrl)) {
intent.agentText = agentText
@@ -2185,15 +2191,23 @@ export function createWechatMpService({
retainUnlinkedRoute = false,
userContext = null,
}) => {
if (forceNew) {
await userAuth.clearWechatAgentRoute(config.appId, openid);
}
const workingDir = await userAuth.resolveWorkingDir(userId);
let sessionPolicy =
await userAuth.getAgentSessionPolicy(userId);
const publishLayout = await userAuth.getUserPublishLayout(userId);
const addressName = resolveWechatAddressName(userContext);
let carriedSessionContentForNewSession = '';
if (forceNew) {
const routeBeforeForceNew = await userAuth.getWechatAgentRoute(config.appId, openid);
if (routeBeforeForceNew?.agentSessionId) {
carriedSessionContentForNewSession = await fetchLastSubstantiveAssistantFromSession(
fetchForSession,
routeBeforeForceNew.agentSessionId,
);
rememberedWechatContexts.delete(routeBeforeForceNew.agentSessionId);
}
await userAuth.clearWechatAgentRoute(config.appId, openid);
}
const existingRoute = await userAuth.getWechatAgentRoute(config.appId, openid);
if (existingRoute?.agentSessionId) {
const now = Date.now();
@@ -2405,6 +2419,53 @@ export function createWechatMpService({
};
};
const rotateWechatSessionIfImagePolluted = async ({
userId,
openid,
sessionId,
user,
carriedSessionContent = '',
}) => {
if (!sessionId) {
return { sessionId, carriedSessionContent, rotated: false };
}
try {
const response = await fetchForSession(
sessionId,
`/sessions/${encodeURIComponent(sessionId)}`,
);
if (!response.ok) {
return { sessionId, carriedSessionContent, rotated: false };
}
const payload = await readJsonResponse(response);
const conversation = Array.isArray(payload?.conversation) ? payload.conversation : [];
if (!conversationHasImageUrlContent(conversation)) {
return { sessionId, carriedSessionContent, rotated: false };
}
logger.warn?.('WeChat MP rotating image-polluted agent session before reply:', {
agentSessionId: sessionId,
conversationLength: conversation.length,
});
const nextRoute = await ensureWechatAgentSession({
userId,
openid,
forceNew: true,
userContext: user,
});
const nextCarried = String(carriedSessionContent ?? '').trim()
|| String(nextRoute.carriedSessionContent ?? '').trim();
return {
sessionId: nextRoute.sessionId,
carriedSessionContent: nextCarried,
rotated: true,
isNewSession: nextRoute.isNewSession,
};
} catch (err) {
logger.warn?.('WeChat MP image-polluted session rotate skipped:', err);
return { sessionId, carriedSessionContent, rotated: false };
}
};
const refreshWechatSessionSnapshot = async (sessionId, userId) => {
if (!sessionId || !userId || typeof refreshSessionSnapshot !== 'function') return;
try {
@@ -2712,6 +2773,26 @@ export function createWechatMpService({
return { sessionId };
}
await rememberWechatUserContext(sessionId, user, { forceBootstrap: route.isNewSession });
const pollutionRotation = await rotateWechatSessionIfImagePolluted({
userId: user.userId,
openid: inbound.fromUserName,
sessionId,
user,
carriedSessionContent,
});
if (pollutionRotation.rotated) {
sessionId = pollutionRotation.sessionId;
route = {
...route,
sessionId,
isNewSession: pollutionRotation.isNewSession ?? true,
};
if (pollutionRotation.carriedSessionContent) {
carriedSessionContent = pollutionRotation.carriedSessionContent;
}
await ensureSessionProvider(sessionId);
await rememberWechatUserContext(sessionId, user, { forceBootstrap: true });
}
if (
wechatIntent.kind === 'page.generate'
&& sessionPageContinuation
@@ -3177,7 +3258,11 @@ export function createWechatMpService({
await ensureSessionProvider(sessionId);
await rememberWechatUserContext(sessionId, user, { forceBootstrap: route.isNewSession });
if (historicalImageError) {
prepareWechatIntentForHistoricalImageRetry(intent);
prepareWechatIntentForHistoricalImageRetry(intent, {
fallbackImageUrl: resolveWechatRecentMediaPublicUrl(
recentMediaByOpenid.get(String(inbound.fromUserName ?? '').trim()),
),
});
}
const retryId = crypto.randomUUID();
const retryStartedAt = Date.now();