154 lines
7.6 KiB
JavaScript
154 lines
7.6 KiB
JavaScript
import { buildAutoChatSkillPrefix, isPageDataIntent } from '../../chat-skills.mjs';
|
||
import { shouldUseScheduleAssistant } from '../../schedule-intent.mjs';
|
||
import { buildCurrentTimeAgentPrefix } from '../../user-memory-profile.mjs';
|
||
import { isPageGenerateText, wantsDocxDownload } from '../intent/patterns.mjs';
|
||
import { buildWechatStandaloneImageInstruction } from '../image-generation-policy.mjs';
|
||
|
||
export function buildWechatAgentPrompt(intent, { grantedSkills = [], imagePolicy = null } = {}) {
|
||
const msgType = String(intent?.msgType ?? 'text');
|
||
const agentText = intent?.agentText ?? intent?.content ?? '';
|
||
const autoSkillPrefix =
|
||
msgType === 'text' || msgType === 'voice'
|
||
? buildAutoChatSkillPrefix(agentText, grantedSkills)
|
||
: '';
|
||
const docxDownloadHint =
|
||
isPageGenerateText(agentText) && wantsDocxDownload(agentText)
|
||
? [
|
||
'【Word 下载要求】用户明确要求页面里可下载 Word / docx。',
|
||
'开始前必须先调用 `load_skill` → `docx-generate`,并按技能说明生成目标 `public/*.docx`。',
|
||
'生成后必须确认目标 `.docx` 已存在,再调用 `load_skill` → `static-page-publish` 写 `public/*.html`。',
|
||
'HTML 中只能用同目录相对路径链接该文档;禁止只写下载按钮却没有先把 `.docx` 落盘。',
|
||
'',
|
||
].join('\n')
|
||
: '';
|
||
const pagePublishHint = isPageGenerateText(agentText)
|
||
? [
|
||
'【页面发布技能要求】这条消息是在生成可访问 HTML 页面。',
|
||
'开始前必须先调用 `load_skill` → `static-page-publish`,不能省略,也不能写完页面后再补调。',
|
||
'随后必须用 sandbox-fs 的 `write_file` / `edit_file` 写入 `public/*.html`。',
|
||
'禁止用 shell / cat / heredoc / echo / cp 写入 HTML:shell 在容器内执行,文件不会出现在公网 MindSpace 路径,用户会收到「文件不存在」。',
|
||
'在回复用户前,确认 `public/` 下目标 HTML 已通过 write_file 落盘;没有落盘就不要发送链接或说「已发布」。',
|
||
'最终只给用户一个正式域名的唯一正确链接;不要输出错误域名、备用链接或让用户手动保存文件。',
|
||
'',
|
||
].join('\n')
|
||
: '';
|
||
const pageDataCollectHint = isPageDataIntent(agentText)
|
||
? [
|
||
'【Page Data 问卷/数据收集要求】这条消息需要可提交、可持久化、可后台查看的数据页面。',
|
||
'开始前必须先调用 `load_skill` → `page-data-collect`,并按技能完成:建表 → register dataset → 写 HTML(必须引入 /assets/page-data-client.js)→ private_data_bind_workspace_page。',
|
||
'禁止 localStorage / 浏览器本地存储 fallback;禁止自建 Express 或独立端口 API。',
|
||
'交付链接必须使用 bind 返回的 workspaceUrl(/MindSpace/<用户ID>/public/...),禁止给用户 /u/用户名/pages/... 发布路由链接。',
|
||
'问卷页与后台页必须分别 bind(public insert + password read);未完成 bind 前不要发送链接或说「已发布/可提交」。',
|
||
'',
|
||
].join('\n')
|
||
: '';
|
||
const scheduleTimezone = process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai';
|
||
const currentTimeHint = buildCurrentTimeAgentPrefix({ timezone: scheduleTimezone });
|
||
const scheduleAssistantHint = shouldUseScheduleAssistant(agentText)
|
||
? [
|
||
'【日程技能要求】这条消息涉及待办、提醒或日程。',
|
||
'开始前先加载 `schedule-assistant` skill,并严格按 skill 里的边界执行。',
|
||
'写入工具时优先使用 startLocal / endLocal / remindLocal(YYYY-MM-DD HH:mm),不要自行估算 Unix 毫秒。',
|
||
'需要到点提醒时,优先在一次 schedule_create_item 中同时传 startLocal 与 remindLocal;不要只创建事项就结束。',
|
||
'只有在 `schedule_create_item` / `schedule_create_reminder` 等工具成功返回后,才能告诉用户“已经设置好了”。',
|
||
intent?.msgId ? `调用 schedule_create_item 时必须传入 sourceMessageId: ${intent.msgId}` : '',
|
||
'',
|
||
].filter(Boolean).join('\n')
|
||
: '';
|
||
const imageGenerationHint = buildWechatStandaloneImageInstruction(imagePolicy, {
|
||
sourceMessageId: intent?.msgId,
|
||
});
|
||
|
||
if (msgType === 'voice') {
|
||
return [
|
||
docxDownloadHint,
|
||
pageDataCollectHint,
|
||
currentTimeHint,
|
||
scheduleAssistantHint,
|
||
imageGenerationHint,
|
||
'【微信服务号语音消息】用户通过语音输入,以下是微信识别结果。',
|
||
'',
|
||
`用户语音识别文本:${autoSkillPrefix}${String(agentText).trim()}`,
|
||
]
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
}
|
||
if (msgType === 'image') {
|
||
return [
|
||
docxDownloadHint,
|
||
currentTimeHint,
|
||
scheduleAssistantHint,
|
||
'【微信服务号图片消息】用户发送了图片。',
|
||
'如用户没有明确要求,请先根据图片内容给出简短理解,并询问下一步。',
|
||
'',
|
||
String(agentText).trim(),
|
||
]
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
}
|
||
if (msgType === 'file') {
|
||
const attachment = intent?.attachment ?? {};
|
||
return [
|
||
currentTimeHint,
|
||
'【微信服务号文件消息】用户发送了需要解析的 Office 文件。',
|
||
attachment.filename ? `文件名:${attachment.filename}` : '',
|
||
attachment.publicUrl ? `文件链接:${attachment.publicUrl}` : '',
|
||
'请复用 H5 附件分析结果回答;Excel 在专用工具可用时必须优先使用 Excel 工具读取完整工作簿。',
|
||
'',
|
||
String(agentText).trim(),
|
||
]
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
}
|
||
if (msgType === 'location') {
|
||
const location = intent?.location ?? {};
|
||
return [
|
||
currentTimeHint,
|
||
scheduleAssistantHint,
|
||
'【微信服务号位置消息】用户发送了当前位置。',
|
||
location.label ? `地址:${location.label}` : '',
|
||
location.latitude !== null && location.latitude !== undefined
|
||
? `纬度:${location.latitude}`
|
||
: '',
|
||
location.longitude !== null && location.longitude !== undefined
|
||
? `经度:${location.longitude}`
|
||
: '',
|
||
'请结合位置回答用户可能的路线、附近、行程或提醒需求;如果意图不明确,先简短询问。',
|
||
]
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
}
|
||
if (msgType === 'link') {
|
||
const link = intent?.link ?? {};
|
||
return [
|
||
currentTimeHint,
|
||
scheduleAssistantHint,
|
||
'【微信服务号链接消息】用户分享了链接。',
|
||
link.title ? `标题:${link.title}` : '',
|
||
link.description ? `描述:${link.description}` : '',
|
||
link.url ? `URL:${link.url}` : '',
|
||
]
|
||
.filter(Boolean)
|
||
.join('\n');
|
||
}
|
||
const content = String(agentText).trim();
|
||
const lines = [currentTimeHint];
|
||
if (intent?.sessionAction === 'ignore_previous') {
|
||
lines.push(
|
||
'【上下文边界】用户要求忽略此前相关内容;本轮只根据当前消息和必要的系统信息回答,不要引用或延续被忽略的历史话题。',
|
||
);
|
||
}
|
||
if (docxDownloadHint) lines.push(docxDownloadHint);
|
||
if (pageDataCollectHint) lines.push(pageDataCollectHint);
|
||
if (pagePublishHint) lines.push(pagePublishHint);
|
||
if (scheduleAssistantHint) lines.push(scheduleAssistantHint);
|
||
if (imageGenerationHint) lines.push(imageGenerationHint);
|
||
lines.push(
|
||
'【微信服务号新消息】请只回答下面这条用户消息,不要主动延续无关的历史话题。',
|
||
'若用户只是在测试连通性,请一句话确认收到即可,不要展开旧任务。',
|
||
'',
|
||
`用户消息:${autoSkillPrefix}${content}`,
|
||
);
|
||
return lines.join('\n');
|
||
}
|