fix: require write_file for WeChat immediate-context page requests
Memind CI / Test, build, and release guards (push) Failing after 12m9s
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:
+162
-130
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user