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 { buildWechatAgentPrompt } from './wechat/prompts/chat-general.mjs';
|
||||||
import {
|
import {
|
||||||
buildPageGenerateAgentPrompt,
|
buildPageGenerateAgentPrompt,
|
||||||
|
buildImmediateContextPageRepairPrompt,
|
||||||
buildPagePublishFailureText,
|
buildPagePublishFailureText,
|
||||||
} from './wechat/prompts/page-generate.mjs';
|
} from './wechat/prompts/page-generate.mjs';
|
||||||
import {
|
import {
|
||||||
@@ -2424,18 +2425,19 @@ export function createWechatMpService({
|
|||||||
const agentReplyTimeoutMs = reliabilityEnabled ? config.agentReplyTimeoutMs : 0;
|
const agentReplyTimeoutMs = reliabilityEnabled ? config.agentReplyTimeoutMs : 0;
|
||||||
const resetCandidate =
|
const resetCandidate =
|
||||||
intent.msgType === 'text' || intent.msgType === 'voice' ? intent.agentText : '';
|
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 isPageDataRequest = isWechatPageDataTask(resetCandidate);
|
||||||
const htmlArtifactDeliveryExpected = shouldDeliverWechatHtmlArtifacts(wechatIntent, intent);
|
const htmlArtifactDeliveryExpected = shouldDeliverWechatHtmlArtifacts(wechatIntent, intent);
|
||||||
const immediateContextFollowup =
|
const immediateContextFollowup =
|
||||||
isWechatImmediateContextFollowup(wechatIntent, resetCandidate);
|
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);
|
const forceNew = shouldForceNewWechatAgentSession(wechatIntent, resetCandidate);
|
||||||
let route = await ensureWechatAgentSession({
|
let route = await ensureWechatAgentSession({
|
||||||
userId: user.userId,
|
userId: user.userId,
|
||||||
@@ -2486,135 +2488,165 @@ export function createWechatMpService({
|
|||||||
preferImmediateContext: immediateContextFollowup,
|
preferImmediateContext: immediateContextFollowup,
|
||||||
})
|
})
|
||||||
: buildWechatAgentPrompt(intent, { imagePolicy });
|
: buildWechatAgentPrompt(intent, { imagePolicy });
|
||||||
const reply = await executeSessionReply(
|
const maxImmediateContextPageAttempts =
|
||||||
(pathname, init) => fetchForSession(sessionId, pathname, init),
|
wechatIntent.kind === 'page.generate' && immediateContextFollowup ? 2 : 1;
|
||||||
sessionId,
|
let reply;
|
||||||
requestId,
|
let publishArtifacts = [];
|
||||||
agentPrompt,
|
let confirmedArtifacts = [];
|
||||||
buildIntentMetadata(intent, {
|
let verifiedArtifacts = [];
|
||||||
mediaAnalysisEnabled,
|
let generatedImages = [];
|
||||||
imagePolicy,
|
let linkExistsForRequest = linkExists;
|
||||||
pgRequired: isPageDataRequest,
|
for (let pageAttempt = 1; pageAttempt <= maxImmediateContextPageAttempts; pageAttempt += 1) {
|
||||||
}),
|
const activeAgentPrompt =
|
||||||
{
|
pageAttempt === 1 ? agentPrompt : buildImmediateContextPageRepairPrompt(intent);
|
||||||
prepareUserMessage: (userMessage) => prepareWechatAgentUserMessage({
|
const activeRequestId = pageAttempt === 1 ? requestId : crypto.randomUUID();
|
||||||
userId: user.userId,
|
reply = await executeSessionReply(
|
||||||
sessionId,
|
(pathname, init) => fetchForSession(sessionId, pathname, init),
|
||||||
userMessage,
|
sessionId,
|
||||||
preserveAgentPrompt: immediateContextFollowup,
|
activeRequestId,
|
||||||
|
activeAgentPrompt,
|
||||||
|
buildIntentMetadata(intent, {
|
||||||
|
mediaAnalysisEnabled,
|
||||||
|
imagePolicy,
|
||||||
|
pgRequired: isPageDataRequest,
|
||||||
}),
|
}),
|
||||||
submitReply: submitSessionReply
|
{
|
||||||
? ({ requestId: replyRequestId, userMessage }) =>
|
prepareUserMessage: (userMessage) => prepareWechatAgentUserMessage({
|
||||||
submitSessionReply({
|
userId: user.userId,
|
||||||
userId: user.userId,
|
sessionId,
|
||||||
sessionId,
|
userMessage,
|
||||||
requestId: replyRequestId,
|
preserveAgentPrompt: immediateContextFollowup,
|
||||||
userMessage,
|
}),
|
||||||
options: { requireHistoricalImageIsolation: true },
|
submitReply: submitSessionReply
|
||||||
})
|
? ({ requestId: replyRequestId, userMessage }) =>
|
||||||
: null,
|
submitSessionReply({
|
||||||
timeoutMs: agentReplyTimeoutMs,
|
userId: user.userId,
|
||||||
},
|
sessionId,
|
||||||
);
|
requestId: replyRequestId,
|
||||||
const generatedImages = collectWechatGeneratedImages(replyRequestMessages(reply));
|
userMessage,
|
||||||
if (imagePolicy.standaloneImageMode === 'required' && generatedImages.length === 0) {
|
options: { requireHistoricalImageIsolation: true },
|
||||||
const error = new Error('图片生成没有获得本轮新的有效位图,请稍后重试');
|
})
|
||||||
error.code = 'WECHAT_IMAGE_GENERATION_REQUIRED';
|
: null,
|
||||||
throw error;
|
timeoutMs: agentReplyTimeoutMs,
|
||||||
}
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
fallback: linkExists,
|
|
||||||
});
|
|
||||||
const hasValidLinkInReply =
|
|
||||||
hasValidReplyLink ||
|
|
||||||
await hasAnyValidPublishedHtmlLink(
|
|
||||||
reply?.text,
|
|
||||||
linkExistsForRequest,
|
|
||||||
{ confirmedArtifacts },
|
|
||||||
);
|
);
|
||||||
const replyHasPublicLinks = hasAnyPublicHtmlLink(reply?.text);
|
generatedImages = collectWechatGeneratedImages(replyRequestMessages(reply));
|
||||||
const suspiciousPublishClaim =
|
if (imagePolicy.standaloneImageMode === 'required' && generatedImages.length === 0) {
|
||||||
expectedArtifacts.length === 0 &&
|
const error = new Error('图片生成没有获得本轮新的有效位图,请稍后重试');
|
||||||
recentArtifacts.length === 0 &&
|
error.code = 'WECHAT_IMAGE_GENERATION_REQUIRED';
|
||||||
(await isSuspiciousHtmlPublishClaimReply(reply, intent, { linkExists: linkExistsForRequest }));
|
throw error;
|
||||||
const htmlGenerationNeedsRetry = shouldRetryHtmlGenerationReply({
|
}
|
||||||
reply,
|
const {
|
||||||
intent,
|
publishedArtifacts,
|
||||||
confirmedArtifacts,
|
expectedArtifacts,
|
||||||
hasValidLinkInReply,
|
recentArtifacts,
|
||||||
});
|
confirmedArtifacts: resolvedConfirmedArtifacts,
|
||||||
const bareCompletionReply = isSuspiciousBareCompletionReply(reply, intent);
|
verifiedArtifacts: resolvedVerifiedArtifacts,
|
||||||
let publishArtifacts =
|
validReplyUrls,
|
||||||
htmlArtifactDeliveryExpected || publishedArtifacts.length > 0
|
hasValidReplyLink,
|
||||||
? selectSendableHtmlArtifacts({ verifiedArtifacts, confirmedArtifacts })
|
} = await resolveHtmlPublishArtifacts({
|
||||||
: [];
|
|
||||||
|
|
||||||
if (wechatIntent.kind === 'page.generate') {
|
|
||||||
const pageOutcome = resolvePageGenerateOutcome({
|
|
||||||
reply,
|
reply,
|
||||||
confirmedArtifacts,
|
intent,
|
||||||
verifiedArtifacts,
|
requestStartedAt,
|
||||||
suspiciousPublishClaim,
|
userId: user.userId,
|
||||||
bareCompletionReply,
|
sessionId,
|
||||||
htmlGenerationNeedsRetry,
|
onPageGenerated,
|
||||||
replyHasPublicLinks,
|
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');
|
throw new Error('stale_session_poisoned_completion');
|
||||||
}
|
}
|
||||||
if (pageOutcome.action === 'fail') {
|
break;
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
await reviewWechatPageDataArtifacts({
|
await reviewWechatPageDataArtifacts({
|
||||||
user,
|
user,
|
||||||
|
|||||||
+25
-1
@@ -29,7 +29,8 @@ import {
|
|||||||
WECHAT_CUSTOMER_TEXT_MAX_BYTES,
|
WECHAT_CUSTOMER_TEXT_MAX_BYTES,
|
||||||
} from './wechat-mp.mjs';
|
} from './wechat-mp.mjs';
|
||||||
import { createChatIntentRouter } from './chat-intent-router.mjs';
|
import { createChatIntentRouter } from './chat-intent-router.mjs';
|
||||||
import { buildPageGenerateAgentPrompt } from './wechat/prompts/page-generate.mjs';
|
import { buildPageGenerateAgentPrompt, buildImmediateContextPageRepairPrompt } from './wechat/prompts/page-generate.mjs';
|
||||||
|
import { resolveWechatImageGenerationPolicy, WECHAT_PAGE_THUMBNAIL_MODE } from './wechat/image-generation-policy.mjs';
|
||||||
import {
|
import {
|
||||||
ensureWechatFreshPageThumbnailsAtWorkspace,
|
ensureWechatFreshPageThumbnailsAtWorkspace,
|
||||||
prepareWechatHtmlDeliveryAtWorkspace,
|
prepareWechatHtmlDeliveryAtWorkspace,
|
||||||
@@ -355,7 +356,30 @@ test('WeChat immediate-context page guard does not match complete page requests'
|
|||||||
});
|
});
|
||||||
assert.match(guardedPrompt, /即时上下文优先/);
|
assert.match(guardedPrompt, /即时上下文优先/);
|
||||||
assert.match(guardedPrompt, /禁止用长期记忆、历史偏好或旧任务替换主题/);
|
assert.match(guardedPrompt, /禁止用长期记忆、历史偏好或旧任务替换主题/);
|
||||||
|
assert.match(guardedPrompt, /write_file 硬门槛/);
|
||||||
|
assert.match(guardedPrompt, /禁止只调用 load_skill、todo_write 或 generate_image 就回复/);
|
||||||
assert.doesNotMatch(ordinaryPrompt, /即时上下文优先/);
|
assert.doesNotMatch(ordinaryPrompt, /即时上下文优先/);
|
||||||
|
assert.doesNotMatch(ordinaryPrompt, /write_file 硬门槛/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('WeChat immediate-context page skips fresh thumbnail requirement', () => {
|
||||||
|
const policy = resolveWechatImageGenerationPolicy({
|
||||||
|
text: '把刚才的诗做成页面',
|
||||||
|
isPageGenerate: true,
|
||||||
|
requireFreshPageThumbnail: false,
|
||||||
|
});
|
||||||
|
assert.equal(policy.pageThumbnailMode, WECHAT_PAGE_THUMBNAIL_MODE.AUTO);
|
||||||
|
const prompt = buildPageGenerateAgentPrompt(
|
||||||
|
{ agentText: '把刚才的诗做成页面' },
|
||||||
|
{ preferImmediateContext: true, imagePolicy: policy },
|
||||||
|
);
|
||||||
|
assert.doesNotMatch(prompt, /服务号页面新缩略图硬要求/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('WeChat immediate-context page repair prompt requires write_file', () => {
|
||||||
|
const repairPrompt = buildImmediateContextPageRepairPrompt({ agentText: '把刚才的诗做成页面' });
|
||||||
|
assert.match(repairPrompt, /未 write_file 落盘 HTML/);
|
||||||
|
assert.match(repairPrompt, /禁止再次只 generate_image/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('WeChat immediate-context page retains an unlinked route with adjacent snapshot', async () => {
|
test('WeChat immediate-context page retains an unlinked route with adjacent snapshot', async () => {
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ export function buildPageGenerateAgentPrompt(
|
|||||||
const immediateContextBlock = preferImmediateContext
|
const immediateContextBlock = preferImmediateContext
|
||||||
? [
|
? [
|
||||||
'【即时上下文优先】当前需求是对本会话紧邻内容的续作。',
|
'【即时上下文优先】当前需求是对本会话紧邻内容的续作。',
|
||||||
'“这个/刚才/做成页面”等指代只能解析为当前会话最近一轮内容,禁止用长期记忆、历史偏好或旧任务替换主题。',
|
'“这个/刚才/做成页面”等指代只能解析为当前会话最近一条 assistant 回复中的完整正文(如诗全文、文章全文),禁止用长期记忆、历史偏好或旧任务替换主题。',
|
||||||
'如果当前会话没有可辨识的紧邻内容,必须请用户补充主题,禁止猜测并生成其他页面。',
|
'如果当前会话没有可辨识的紧邻内容,必须请用户补充主题,禁止猜测并生成其他页面。',
|
||||||
|
'【write_file 硬门槛】必须先 `load_skill` → `static-page-publish`,再用 `write_file`/`edit_file` 把紧邻正文完整写入 `public/*.html` 后才能结束。',
|
||||||
|
'禁止只调用 load_skill、todo_write 或 generate_image 就回复;没有 write_file 落盘 HTML 视为未完成。',
|
||||||
|
'缩略图/配图可选:优先完成 HTML 落盘与 mindspace-cover;没有 hero 图时 cover 可用 CSS 渐变或 omit,不要卡在 generate_image。',
|
||||||
'',
|
'',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
: '';
|
: '';
|
||||||
@@ -84,6 +87,19 @@ export function buildPageGenerateAgentPrompt(
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildImmediateContextPageRepairPrompt(intent) {
|
||||||
|
const topic = String(intent?.agentText ?? intent?.displayText ?? '').trim();
|
||||||
|
return [
|
||||||
|
'【即时上下文页面补写】上一轮页面生成未完成:已 load_skill 但未 write_file 落盘 HTML。',
|
||||||
|
'请立即读取本会话最近一条 assistant 回复中的完整正文,将其排版为完整静态页。',
|
||||||
|
'步骤:确认 static-page-publish 已加载 → 用 write_file 写入 public/*.html(含 mindspace-cover、platform-brand 页脚)→ 确认落盘后再回复唯一正式链接。',
|
||||||
|
'禁止再次只 generate_image 或只回复文字;本轮必须完成 write_file。',
|
||||||
|
topic ? `用户原话:${topic}` : '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
export function buildPagePublishFailureText({
|
export function buildPagePublishFailureText({
|
||||||
missingSharePreview = false,
|
missingSharePreview = false,
|
||||||
missingPlatformBrand = false,
|
missingPlatformBrand = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user