Fix vector recall pending user message

This commit is contained in:
2026-01-29 01:17:37 +08:00
parent d1caf01334
commit 3313b5efa7
3 changed files with 217 additions and 185 deletions

View File

@@ -1,17 +1,10 @@
// ═══════════════════════════════════════════════════════════════════════════
// Story Summary - Prompt Injection (Final Clean Version)
// - 注入只在 GENERATION_STARTED 发生(由 story-summary.js 调用)
// - 向量关闭:注入全量总结(世界/事件/弧光
// - 向量开启:召回 + 预算装配注入
// - 没有“快速注入”写入 extension_prompts避免覆盖/残留/竞态
// - 仅负责“构建注入文本”,不负责写入 extension_prompts
// - 注入发生在 story-summary.js 的 CHAT_COMPLETION_PROMPT_READY最终 messages 已裁剪
// ═══════════════════════════════════════════════════════════════════════════
import { getContext } from "../../../../../../extensions.js";
import {
extension_prompts,
extension_prompt_types,
extension_prompt_roles,
} from "../../../../../../../script.js";
import { xbLog } from "../../../core/debug-core.js";
import { getSummaryStore } from "../data/store.js";
import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js";
@@ -19,7 +12,6 @@ import { recallMemory, buildQueryText } from "../vector/recall.js";
import { getChunksByFloors, getAllChunkVectors, getAllEventVectors, getMeta } from "../vector/chunk-store.js";
const MODULE_ID = "summaryPrompt";
const SUMMARY_PROMPT_KEY = "LittleWhiteBox_StorySummary";
// ─────────────────────────────────────────────────────────────────────────────
// 召回失败提示节流(避免连续生成刷屏)
@@ -137,7 +129,8 @@ function formatArcLine(a) {
// 完整 chunk 输出(不截断)
function formatChunkFullLine(c) {
const speaker = c.isUser ? "{{user}}" : "{{char}}";
const { name1, name2 } = getContext();
const speaker = c.isUser ? (name1 || "用户") : (name2 || "角色");
return ` #${c.floor + 1} [${speaker}] ${String(c.text || "").trim()}`;
}
@@ -299,23 +292,19 @@ function buildNonVectorPrompt(store) {
);
}
export async function injectNonVectorPrompt(postToFrame = null) {
export function buildNonVectorPromptText() {
if (!getSettings().storySummary?.enabled) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return "";
}
const { chat } = getContext();
const store = getSummaryStore();
if (!store?.json) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return "";
}
let text = buildNonVectorPrompt(store);
if (!text.trim()) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return "";
}
// wrapper沿用面板设置
@@ -323,21 +312,7 @@ export async function injectNonVectorPrompt(postToFrame = null) {
if (cfg.trigger?.wrapperHead) text = cfg.trigger.wrapperHead + "\n" + text;
if (cfg.trigger?.wrapperTail) text = text + "\n" + cfg.trigger.wrapperTail;
const lastIdx = store.lastSummarizedMesId ?? 0;
let depth = (chat?.length || 0) - lastIdx - 1;
if (depth < 0) depth = 0;
if (cfg.trigger?.forceInsertAtEnd) depth = 10000;
extension_prompts[SUMMARY_PROMPT_KEY] = {
value: text,
position: extension_prompt_types.IN_CHAT,
depth,
role: extension_prompt_roles.SYSTEM,
};
if (postToFrame) {
postToFrame({ type: "RECALL_LOG", text: "\n[Non-vector] Injected full summary prompt.\n" });
}
return text;
}
// ─────────────────────────────────────────────────────────────
@@ -706,19 +681,17 @@ async function attachEvidenceToCausalEvents(causalEvents, eventVectorMap, chunkV
// ✅ 向量模式:召回 + 注入(供 story-summary.js 在 GENERATION_STARTED 调用)
// ─────────────────────────────────────────────────────────────────────────────
export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
const { postToFrame = null, echo = null } = hooks;
export async function buildVectorPromptText(excludeLastAi = false, hooks = {}) {
const { postToFrame = null, echo = null, pendingUserMessage = null } = hooks;
if (!getSettings().storySummary?.enabled) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return { text: "", logText: "" };
}
const { chat } = getContext();
const store = getSummaryStore();
if (!store?.json) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return { text: "", logText: "" };
}
const allEvents = store.json.events || [];
@@ -726,14 +699,12 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
const length = chat?.length || 0;
if (lastIdx >= length) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return { text: "", logText: "" };
}
const vectorCfg = getVectorConfig();
if (!vectorCfg?.enabled) {
// 向量没开,不该走这条
return;
return { text: "", logText: "" };
}
const { chatId } = getContext();
@@ -745,7 +716,10 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
try {
const queryText = buildQueryText(chat, 2, excludeLastAi);
recallResult = await recallMemory(queryText, allEvents, vectorCfg, { excludeLastAi });
recallResult = await recallMemory(queryText, allEvents, vectorCfg, {
excludeLastAi,
pendingUserMessage,
});
recallResult = {
...recallResult,
@@ -808,9 +782,7 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
});
}
// 清空本次注入,避免残留误导
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return { text: "", logText: `\n[Vector Recall Failed]\n${String(e?.stack || e?.message || e)}\n` };
}
// 成功但结果为空:也提示,并清空注入(不降级)
@@ -831,8 +803,7 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
text: "\n[Vector Recall Empty]\nNo recall candidates / vectors not ready.\n",
});
}
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
return { text: "", logText: "\n[Vector Recall Empty]\nNo recall candidates / vectors not ready.\n" };
}
// 拼装向量 prompt
@@ -844,50 +815,17 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
meta
);
// 写入 extension_prompts真正注入
await writePromptToExtensionPrompts(promptText, store, chat);
// wrapper沿用面板设置——必须补回否则语义回退
const cfg = getSummaryPanelConfig();
let finalText = String(promptText || "");
if (cfg.trigger?.wrapperHead) finalText = cfg.trigger.wrapperHead + "\n" + finalText;
if (cfg.trigger?.wrapperTail) finalText = finalText + "\n" + cfg.trigger.wrapperTail;
// 发给涌现窗口:召回报告 + 装配报告
if (postToFrame) {
const recallLog = recallResult.logText || "";
postToFrame({ type: "RECALL_LOG", text: recallLog + (injectionLogText || "") });
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 写入 extension_prompts统一入口
// ─────────────────────────────────────────────────────────────────────────────
async function writePromptToExtensionPrompts(text, store, chat) {
const cfg = getSummaryPanelConfig();
let finalText = String(text || "");
if (cfg.trigger?.wrapperHead) finalText = cfg.trigger.wrapperHead + "\n" + finalText;
if (cfg.trigger?.wrapperTail) finalText = finalText + "\n" + cfg.trigger.wrapperTail;
if (!finalText.trim()) {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return;
}
const lastIdx = store.lastSummarizedMesId ?? 0;
let depth = (chat?.length || 0) - lastIdx - 1;
if (depth < 0) depth = 0;
if (cfg.trigger?.forceInsertAtEnd) depth = 10000;
extension_prompts[SUMMARY_PROMPT_KEY] = {
value: finalText,
position: extension_prompt_types.IN_CHAT,
depth,
role: extension_prompt_roles.SYSTEM,
};
}
// ─────────────────────────────────────────────────────────────────────────────
// 清理 prompt供 story-summary.js 调用)
// ─────────────────────────────────────────────────────────────────────────────
export function clearSummaryExtensionPrompt() {
delete extension_prompts[SUMMARY_PROMPT_KEY];
return { text: finalText, logText: (recallResult.logText || "") + (injectionLogText || "") };
}