fix: require write_file for WeChat immediate-context page requests
Memind CI / Test, build, and release guards (push) Failing after 12m9s

Skip fresh thumbnail forcing and retry in-session when load_skill completes without HTML落盘, so「把刚才的诗做成页面」can deliver the prior poem as a static page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-28 15:27:23 +08:00
parent e1ad094f93
commit 491a62aed6
3 changed files with 204 additions and 132 deletions
+162 -130
View File
@@ -40,6 +40,7 @@ import {
import { buildWechatAgentPrompt } from './wechat/prompts/chat-general.mjs';
import {
buildPageGenerateAgentPrompt,
buildImmediateContextPageRepairPrompt,
buildPagePublishFailureText,
} from './wechat/prompts/page-generate.mjs';
import {
@@ -2424,18 +2425,19 @@ export function createWechatMpService({
const agentReplyTimeoutMs = reliabilityEnabled ? config.agentReplyTimeoutMs : 0;
const resetCandidate =
intent.msgType === 'text' || intent.msgType === 'voice' ? intent.agentText : '';
const imagePolicy = resolveWechatImageGenerationPolicy({
text: resetCandidate,
isPageGenerate: wechatIntent.kind === 'page.generate',
requireFreshPageThumbnail: config.requireFreshPageThumbnail,
});
// Page Data delivery owns persistent files, datasets and two publication
// policies. Reusing a conversational route here can make a new request
// inspect/retry unrelated historical pages from that session.
const isPageDataRequest = isWechatPageDataTask(resetCandidate);
const htmlArtifactDeliveryExpected = shouldDeliverWechatHtmlArtifacts(wechatIntent, intent);
const immediateContextFollowup =
isWechatImmediateContextFollowup(wechatIntent, resetCandidate);
const imagePolicy = resolveWechatImageGenerationPolicy({
text: resetCandidate,
isPageGenerate: wechatIntent.kind === 'page.generate',
requireFreshPageThumbnail:
config.requireFreshPageThumbnail && !immediateContextFollowup,
});
// Page Data delivery owns persistent files, datasets and two publication
// policies. Reusing a conversational route here can make a new request
// inspect/retry unrelated historical pages from that session.
const forceNew = shouldForceNewWechatAgentSession(wechatIntent, resetCandidate);
let route = await ensureWechatAgentSession({
userId: user.userId,
@@ -2486,135 +2488,165 @@ export function createWechatMpService({
preferImmediateContext: immediateContextFollowup,
})
: buildWechatAgentPrompt(intent, { imagePolicy });
const reply = await executeSessionReply(
(pathname, init) => fetchForSession(sessionId, pathname, init),
sessionId,
requestId,
agentPrompt,
buildIntentMetadata(intent, {
mediaAnalysisEnabled,
imagePolicy,
pgRequired: isPageDataRequest,
}),
{
prepareUserMessage: (userMessage) => prepareWechatAgentUserMessage({
userId: user.userId,
sessionId,
userMessage,
preserveAgentPrompt: immediateContextFollowup,
const maxImmediateContextPageAttempts =
wechatIntent.kind === 'page.generate' && immediateContextFollowup ? 2 : 1;
let reply;
let publishArtifacts = [];
let confirmedArtifacts = [];
let verifiedArtifacts = [];
let generatedImages = [];
let linkExistsForRequest = linkExists;
for (let pageAttempt = 1; pageAttempt <= maxImmediateContextPageAttempts; pageAttempt += 1) {
const activeAgentPrompt =
pageAttempt === 1 ? agentPrompt : buildImmediateContextPageRepairPrompt(intent);
const activeRequestId = pageAttempt === 1 ? requestId : crypto.randomUUID();
reply = await executeSessionReply(
(pathname, init) => fetchForSession(sessionId, pathname, init),
sessionId,
activeRequestId,
activeAgentPrompt,
buildIntentMetadata(intent, {
mediaAnalysisEnabled,
imagePolicy,
pgRequired: isPageDataRequest,
}),
submitReply: submitSessionReply
? ({ requestId: replyRequestId, userMessage }) =>
submitSessionReply({
userId: user.userId,
sessionId,
requestId: replyRequestId,
userMessage,
options: { requireHistoricalImageIsolation: true },
})
: null,
timeoutMs: agentReplyTimeoutMs,
},
);
const generatedImages = collectWechatGeneratedImages(replyRequestMessages(reply));
if (imagePolicy.standaloneImageMode === 'required' && generatedImages.length === 0) {
const error = new Error('图片生成没有获得本轮新的有效位图,请稍后重试');
error.code = 'WECHAT_IMAGE_GENERATION_REQUIRED';
throw error;
}
const {
publishedArtifacts,
verifiedArtifacts,
expectedArtifacts,
recentArtifacts,
confirmedArtifacts,
validReplyUrls,
hasValidReplyLink,
} = await resolveHtmlPublishArtifacts({
reply,
intent,
requestStartedAt,
userId: user.userId,
sessionId,
onPageGenerated,
allowRecentArtifacts: htmlArtifactDeliveryExpected,
htmlDeliveryAuthority,
});
const linkExistsForRequest =
createPreparedPublicHtmlLinkExists({
userId: user.userId,
prepared: {
validReplyUrls,
confirmedArtifacts,
{
prepareUserMessage: (userMessage) => prepareWechatAgentUserMessage({
userId: user.userId,
sessionId,
userMessage,
preserveAgentPrompt: immediateContextFollowup,
}),
submitReply: submitSessionReply
? ({ requestId: replyRequestId, userMessage }) =>
submitSessionReply({
userId: user.userId,
sessionId,
requestId: replyRequestId,
userMessage,
options: { requireHistoricalImageIsolation: true },
})
: null,
timeoutMs: agentReplyTimeoutMs,
},
fallback: linkExists,
});
const hasValidLinkInReply =
hasValidReplyLink ||
await hasAnyValidPublishedHtmlLink(
reply?.text,
linkExistsForRequest,
{ confirmedArtifacts },
);
const replyHasPublicLinks = hasAnyPublicHtmlLink(reply?.text);
const suspiciousPublishClaim =
expectedArtifacts.length === 0 &&
recentArtifacts.length === 0 &&
(await isSuspiciousHtmlPublishClaimReply(reply, intent, { linkExists: linkExistsForRequest }));
const htmlGenerationNeedsRetry = shouldRetryHtmlGenerationReply({
reply,
intent,
confirmedArtifacts,
hasValidLinkInReply,
});
const bareCompletionReply = isSuspiciousBareCompletionReply(reply, intent);
let publishArtifacts =
htmlArtifactDeliveryExpected || publishedArtifacts.length > 0
? selectSendableHtmlArtifacts({ verifiedArtifacts, confirmedArtifacts })
: [];
if (wechatIntent.kind === 'page.generate') {
const pageOutcome = resolvePageGenerateOutcome({
generatedImages = collectWechatGeneratedImages(replyRequestMessages(reply));
if (imagePolicy.standaloneImageMode === 'required' && generatedImages.length === 0) {
const error = new Error('图片生成没有获得本轮新的有效位图,请稍后重试');
error.code = 'WECHAT_IMAGE_GENERATION_REQUIRED';
throw error;
}
const {
publishedArtifacts,
expectedArtifacts,
recentArtifacts,
confirmedArtifacts: resolvedConfirmedArtifacts,
verifiedArtifacts: resolvedVerifiedArtifacts,
validReplyUrls,
hasValidReplyLink,
} = await resolveHtmlPublishArtifacts({
reply,
confirmedArtifacts,
verifiedArtifacts,
suspiciousPublishClaim,
bareCompletionReply,
htmlGenerationNeedsRetry,
replyHasPublicLinks,
intent,
requestStartedAt,
userId: user.userId,
sessionId,
onPageGenerated,
allowRecentArtifacts: htmlArtifactDeliveryExpected,
htmlDeliveryAuthority,
});
if (pageOutcome.action === 'session_retry') {
confirmedArtifacts = resolvedConfirmedArtifacts;
verifiedArtifacts = resolvedVerifiedArtifacts;
linkExistsForRequest =
createPreparedPublicHtmlLinkExists({
userId: user.userId,
prepared: {
validReplyUrls,
confirmedArtifacts,
},
fallback: linkExists,
});
const hasValidLinkInReply =
hasValidReplyLink ||
await hasAnyValidPublishedHtmlLink(
reply?.text,
linkExistsForRequest,
{ confirmedArtifacts },
);
const replyHasPublicLinks = hasAnyPublicHtmlLink(reply?.text);
const suspiciousPublishClaim =
expectedArtifacts.length === 0 &&
recentArtifacts.length === 0 &&
(await isSuspiciousHtmlPublishClaimReply(reply, intent, { linkExists: linkExistsForRequest }));
const htmlGenerationNeedsRetry = shouldRetryHtmlGenerationReply({
reply,
intent,
confirmedArtifacts,
hasValidLinkInReply,
});
const bareCompletionReply = isSuspiciousBareCompletionReply(reply, intent);
publishArtifacts =
htmlArtifactDeliveryExpected || publishedArtifacts.length > 0
? selectSendableHtmlArtifacts({ verifiedArtifacts, confirmedArtifacts })
: [];
if (wechatIntent.kind === 'page.generate') {
const pageOutcome = resolvePageGenerateOutcome({
reply,
confirmedArtifacts,
verifiedArtifacts,
suspiciousPublishClaim,
bareCompletionReply,
htmlGenerationNeedsRetry,
replyHasPublicLinks,
});
if (pageOutcome.action === 'session_retry') {
throw new Error('stale_session_poisoned_completion');
}
if (pageOutcome.action === 'fail') {
if (
pageAttempt < maxImmediateContextPageAttempts &&
pageOutcome.reason === 'skill_or_stub'
) {
logger.warn?.('WeChat MP immediate-context page repair retry:', {
sessionId,
userId: user.userId,
pageAttempt,
});
continue;
}
const text = pageOutcome.failureText ?? buildPagePublishFailureText();
try {
await sendCustomerServiceText(inbound.fromUserName, text, user);
} catch (sendErr) {
logger.error?.('WeChat MP page generate failure notice failed:', sendErr);
}
throw markWechatUserNotified(new Error(text));
}
publishArtifacts = pageOutcome.artifacts ?? publishArtifacts;
break;
}
if (looksLikeHtmlGenerationIntent(intent?.agentText)) {
if (
suspiciousPublishClaim ||
bareCompletionReply ||
(htmlGenerationNeedsRetry && replyHasPublicLinks && confirmedArtifacts.length === 0)
) {
throw new Error('stale_session_poisoned_completion');
}
if (htmlGenerationNeedsRetry) {
const text = buildHtmlPublishFailureText();
try {
await sendCustomerServiceText(inbound.fromUserName, text, user);
} catch (sendErr) {
logger.error?.('WeChat MP html publish failure notice failed:', sendErr);
}
throw markWechatUserNotified(new Error(text));
}
} else if (htmlGenerationNeedsRetry || suspiciousPublishClaim) {
throw new Error('stale_session_poisoned_completion');
}
if (pageOutcome.action === 'fail') {
const text = pageOutcome.failureText ?? buildPagePublishFailureText();
try {
await sendCustomerServiceText(inbound.fromUserName, text, user);
} catch (sendErr) {
logger.error?.('WeChat MP page generate failure notice failed:', sendErr);
}
throw markWechatUserNotified(new Error(text));
}
publishArtifacts = pageOutcome.artifacts ?? publishArtifacts;
} else if (looksLikeHtmlGenerationIntent(intent?.agentText)) {
if (
suspiciousPublishClaim ||
bareCompletionReply ||
(htmlGenerationNeedsRetry && replyHasPublicLinks && confirmedArtifacts.length === 0)
) {
throw new Error('stale_session_poisoned_completion');
}
if (htmlGenerationNeedsRetry) {
const text = buildHtmlPublishFailureText();
try {
await sendCustomerServiceText(inbound.fromUserName, text, user);
} catch (sendErr) {
logger.error?.('WeChat MP html publish failure notice failed:', sendErr);
}
throw markWechatUserNotified(new Error(text));
}
} else if (htmlGenerationNeedsRetry || suspiciousPublishClaim) {
throw new Error('stale_session_poisoned_completion');
break;
}
await reviewWechatPageDataArtifacts({
user,