(event)
| 1683 | } |
| 1684 | |
| 1685 | async function buildModelReply(event) { |
| 1686 | const text = stripMentionText(event.text); |
| 1687 | const id = crypto.randomUUID(); |
| 1688 | const outputPath = join(codexTmpDir, `${id}.txt`); |
| 1689 | const quotedContext = formatQuotedContext(event); |
| 1690 | let memoryContext = formatMemoryContext(event, { expandLevel: 0 }); |
| 1691 | const personaContext = formatQqPersonaContext(event); |
| 1692 | const repetitionGuard = state.qq.enhancer.enabled ? buildQqRepetitionGuard(event) : ""; |
| 1693 | const webContext = await buildWebLookupContext(event); |
| 1694 | const stickerCatalog = state.qq.enhancer.enabled ? await buildQqStickerCatalog(qqStickerDir) : []; |
| 1695 | const qqModelImages = getQqModelImageInputs(event, text); |
| 1696 | const shouldInspectImages = qqModelImages.length > 0; |
| 1697 | const imagePaths = shouldInspectImages |
| 1698 | ? await prepareQqModelImages(qqModelImages, { |
| 1699 | outputDir: join(projectDir, "tmp", "qq-images"), |
| 1700 | fetchOneBotImage |
| 1701 | }) |
| 1702 | : []; |
| 1703 | event.imagePaths = imagePaths; |
| 1704 | const buildReplyPrompt = async (memoryBlock, expandLevel = 0, forceLocalReply = false) => [ |
| 1705 | await buildAssistantInstructions(event), |
| 1706 | "", |
| 1707 | personaContext, |
| 1708 | personaContext ? "" : null, |
| 1709 | memoryBlock, |
| 1710 | memoryBlock ? "" : null, |
| 1711 | repetitionGuard, |
| 1712 | repetitionGuard ? "" : null, |
| 1713 | quotedContext, |
| 1714 | quotedContext ? "" : null, |
| 1715 | webContext, |
| 1716 | webContext ? "" : null, |
| 1717 | !forceLocalReply && expandLevel === 0 ? "如果这条消息明显是在追问前文、接上一句、问刚刚发生了什么,而你拿到的最近上下文仍然不够判断,请不要硬猜,直接只输出 [[qq_context_more]] 这个标记,让 Hub 继续向前翻记录后再回答。" : null, |
| 1718 | expandLevel === 0 ? "" : null, |
| 1719 | state.qq.enhancer.enabled && event.proactiveDecision?.ownerContext ? `触发原因:${ownerLabel}刚刚在群里说话,Hub 已扫描上文并发现有你感兴趣的内容。请像看到上文后主动探头一样回应,不要假装${ownerLabel}直接问了你。` : null, |
| 1720 | state.qq.enhancer.enabled && event.proactiveDecision?.ownerContext ? "" : null, |
| 1721 | event.pendingImageRequestText ? `触发原因:${ownerLabel}刚刚说“${event.pendingImageRequestText}”,随后这张 QQ 图片到达。请直接看这张图并回应。` : null, |
| 1722 | event.pendingImageRequestText ? "" : null, |
| 1723 | hasAnyQqImageReference(event) && !shouldInspectImages ? "本条 QQ 消息或引用消息带了图片,但文本兴趣不足或未明确要求看图;Hub 已跳过视觉输入以节省 token。不要声称看过图片内容。" : null, |
| 1724 | shouldInspectImages ? `收到的 QQ 图片:${formatQqImageSummary(qqModelImages)}` : null, |
| 1725 | imagePaths.length ? `可查看的本地图片数量:${imagePaths.length}` : null, |
| 1726 | imagePaths.length ? "请结合图片内容回复。不要过度保守:只要能看清主元素、文字、构图或梗图大意,就先说你看到了什么并给出判断;只有完全无法辨认主体时才说看不清。" : null, |
| 1727 | hasAnyQqImageReference(event) ? "" : null, |
| 1728 | state.qq.enhancer.enabled ? "本地表情包库:" : null, |
| 1729 | state.qq.enhancer.enabled ? formatQqStickerCatalog(stickerCatalog) : null, |
| 1730 | state.qq.enhancer.enabled && stickerCatalog.length ? "表情包库可用时,部署者可以在自定义 profile 或 QQ enhancer 包中说明何时使用 [[qq_sticker:表情包名]];只能选择提示里真实存在的表情包名。" : null, |
| 1731 | "", |
| 1732 | event.type === "private_message" ? "收到的 QQ 私聊:" : "收到的群消息:", |
| 1733 | text || "对方只 @ 了你,没有附加具体内容。", |
| 1734 | "", |
| 1735 | forceLocalReply ? "不要再请求更多上下文,也不要输出任何内部标记。请基于当前消息、引用、图片和已经给到的上下文,给出自然回复。" : null, |
| 1736 | forceLocalReply ? "" : null, |
| 1737 | event.type === "private_message" |
| 1738 | ? "请直接给出要发送到 QQ 私聊里的最终回复。不要追加服务式追问或“我还能继续帮你”的结尾。" |
| 1739 | : "请直接给出要发送到 QQ 群里的最终回复。不要追加服务式追问或“我还能继续帮你”的结尾。" |
| 1740 | ].filter((part) => part != null).join("\n"); |
| 1741 | |
| 1742 | await ensureCodexReplyWorkspace(); |
no test coverage detected