Files
john c7dcc5d002
Memind CI / Test, build, and release guards (push) Failing after 3s
fix(wechat): guard bare page-continuation follow-ups from session loss and memory drift
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>
2026-08-02 18:49:57 +08:00

141 lines
7.8 KiB
JavaScript

import { buildCurrentTimeAgentPrefix } from '../../user-memory-profile.mjs';
import {
isPageDataDevIntent,
isPageDataIntent,
} from '../../chat-skills.mjs';
import { buildWechatPageImageInstruction } from '../image-generation-policy.mjs';
/**
* Service-account-only page generation prompt. Does not use H5 buildAutoChatSkillPrefix.
*/
export function buildPageGenerateAgentPrompt(
intent,
{
wantsDocx = false,
imagePolicy = null,
preferImmediateContext = false,
carriedSessionContent = '',
} = {},
) {
const topic = String(intent?.agentText ?? intent?.displayText ?? '').trim();
const pageDataTask = isPageDataIntent(topic) || isPageDataDevIntent(topic);
const scheduleTimezone = process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai';
const docxBlock = wantsDocx
? [
'【Word 下载】用户要求页面内可下载 Word/docx。',
'先 `load_skill` → `docx-generate` 生成 `public/*.docx`,再写 HTML 并链接同目录文档。',
'',
].join('\n')
: '';
const imageBlock = buildWechatPageImageInstruction(imagePolicy, {
sourceMessageId: intent?.msgId,
});
const immediateContextBlock = preferImmediateContext
? [
'【即时上下文优先】当前需求是对本会话紧邻内容的续作(做成页面、修改页面、重试上次页面或改诗/文章内容)。',
'“这个/刚才/做成页面/修改页面/重试上次页面”等指代只能解析为当前会话最近一条 assistant 回复或最近落盘的 public/*.html,禁止用长期记忆、历史偏好或旧任务替换主题。',
'如果当前会话没有可辨识的紧邻内容,必须请用户补充主题,禁止猜测并生成其他页面。',
'【write_file 硬门槛】必须先 `load_skill` → `static-page-publish`,再用 `write_file`/`edit_file` 写入或更新 `public/*.html` 后才能结束。',
'修改已有页面时必须 edit_file 更新同一 HTML 或明确的新文件,禁止只回复文字说明。',
'禁止只调用 load_skill、todo_write 或 generate_image 就回复;没有 write_file/edit_file 落盘 HTML 视为未完成。',
'缩略图/配图可选:优先完成 HTML 落盘与 mindspace-cover;没有 hero 图时 cover 可用 CSS 渐变或 omit,不要卡在 generate_image。',
'',
].join('\n')
: '';
const carriedSessionBlock = String(carriedSessionContent ?? '').trim()
? [
'【待排版正文(来自上一轮会话,必须以此为主题,禁止改用 Memory 或其他任务)】',
String(carriedSessionContent).trim(),
'',
].join('\n')
: '';
const coverExample = imageBlock
? '<本轮 generate_image 返回的 asset.htmlSrc>'
: 'assets/hero.jpg';
const pageDataBlock = pageDataTask
? [
'【Page Data 强制要求】这不是静态展示页。',
'先 `load_skill` → `page-data-collect`,再按技能执行:private_data_execute 建表 → private_data_register_dataset → 写 HTML → private_data_bind_workspace_page。',
'修复任务必须核对真实表、dataset、policy 与 bind;缺失时必须补齐,禁止只改 HTML。',
'HTML 必须使用 /assets/page-data-client.js 与服务端 authenticate/listRows/insertRow;禁止硬编码口令、本地存储和自建 API。',
'只有 Page Data 提交/读取交付验收通过后才能发送链接。',
'',
].join('\n')
: '';
return [
'【微信服务号 · 页面生成任务】',
'这是服务号专用页面生成,不是普通聊天。必须按步骤完成,未完成前禁止告诉用户“已生成/已发布”。',
'',
docxBlock,
imageBlock,
immediateContextBlock,
carriedSessionBlock,
pageDataBlock,
'步骤(必须全部完成):',
pageDataTask
? '1. 完成 page-data-collect 数据能力后,再调用 `load_skill`,参数 name=`static-page-publish`,补齐视觉与发布要求。'
: '1. 必须先调用 `load_skill`,参数 name=`static-page-publish`。',
'2. 阅读技能说明后,用 sandbox-fs 的 `write_file` 或 `edit_file` 写入 `public/*.html`。',
'3. 禁止 shell/cat/heredoc/cp 写 HTML(不会出现在公网 MindSpace)。',
'4. 写完后确认目标文件已落盘,且内容是用户要的完整页面(不是占位 stub)。',
'5. 在 `<head>` 写入微信分享卡片所需元数据(必须):',
' - `<meta name="description" content="一句话摘要">`',
` - \`<meta name="mindspace-cover" content='{"tag":"主题","accent":"#主色","accent2":"#辅色","subtitle":"卖点","cover":"${coverExample}"}'>\``,
imageBlock
? ' tag/accent/subtitle 必须与页面主题一致;cover 必须引用本轮新生成资产,不得省略、复用旧图或填写占位路径。'
: ' tag/accent/subtitle 必须与页面主题一致;有 hero 图时 cover 指向同目录 raster 图(jpg/png)。',
'6. 交付前运行 `npm run check:mindspace-cover`(或 node scripts/check-mindspace-cover.mjs --user <uid>);有 error 则补元数据后再回复链接。',
'7. 页脚加 `<p data-mindspace-page-tag="platform-brand">TKMind · 智趣</p>`(必须;禁止省略、不要用邮箱/tkmind.ai,也不要把该行 opacity 设太低导致看不见)。',
'8. 回复里只给一个正式域名的唯一正确链接;没有落盘或缺少上述元数据就不要发链接。',
'9. 禁止编造用户未提供的产品指标、客户数量、SLA、认证、案例、排名或承诺;没有可靠依据时使用定性描述,不得杜撰数字。',
'',
buildCurrentTimeAgentPrefix({ timezone: scheduleTimezone }),
`用户需求:${topic}`,
]
.filter(Boolean)
.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({
missingSharePreview = false,
missingPlatformBrand = false,
missingFreshThumbnail = false,
} = {}) {
if (missingFreshThumbnail) {
return [
'这次页面内容已生成,但没有完成服务号要求的本轮新缩略图,所以我先不发页面链接。',
'页面内容已经保留。请稍后回复“重试上次页面”,无需重新描述完整需求。',
].join('\n');
}
if (missingPlatformBrand) {
return [
'这次页面已落盘,但缺少页脚平台品牌标记(data-mindspace-page-tag="platform-brand"),所以我先不发链接。',
'请直接重发一次完整页面需求,我会重新按 static-page-publish 技能生成带 TKMind · 智趣 品牌页脚的页面。',
].join('\n');
}
if (missingSharePreview) {
return [
'这次页面已落盘,但缺少微信分享卡片所需的预览元数据(mindspace-cover 与摘要),所以我先不发链接。',
'请直接重发一次完整页面需求,我会重新按 static-page-publish 技能生成带预览图的页面。',
].join('\n');
}
return [
'这次页面没有按服务号页面技能真正生成成功,所以我先不发占位链接。',
'请直接重发一次完整页面需求,我会重新按 static-page-publish 技能链路生成。',
].join('\n');
}