import assert from "node:assert/strict"; import { installResolveHooks, } from "./helpers/register-hooks-compat.mjs"; const extensionsShimSource = [ "export const extension_settings = {};", "export function getContext(...args) {", " return globalThis.SillyTavern?.getContext?.(...args) || null;", "}", ].join("\n"); const scriptShimSource = [ "export function substituteParamsExtended(text) {", " return String(text ?? '');", "}", ].join("\n"); const extensionsShimUrl = `data:text/javascript,${encodeURIComponent( extensionsShimSource, )}`; const scriptShimUrl = `data:text/javascript,${encodeURIComponent( scriptShimSource, )}`; installResolveHooks([ { specifiers: [ "../../../extensions.js", "../../../../extensions.js", "../../../../../extensions.js", ], url: extensionsShimUrl, }, { specifiers: [ "../../../../script.js", "../../../../../script.js", ], url: scriptShimUrl, }, ]); const originalSillyTavern = globalThis.SillyTavern; const originalEjsTemplate = globalThis.EjsTemplate; const originalMvu = globalThis.Mvu; const originalGetCharWorldbookNames = globalThis.getCharWorldbookNames; const originalGetWorldbook = globalThis.getWorldbook; const originalGetLorebookEntries = globalThis.getLorebookEntries; function createWorldbookEntry({ uid, name, comment = name, content, enabled = true, positionType = "before_character_definition", role = "system", depth = 0, order = 10, strategyType = "constant", keys = [], keysSecondary = [], }) { return { uid, name, comment, content, enabled, position: { type: positionType, role, depth, order, }, strategy: { type: strategyType, keys, keys_secondary: { logic: "and_any", keys: keysSecondary }, }, probability: 100, extra: {}, }; } function createConstantWorldbookEntry(uid, name, content, comment = name) { return createWorldbookEntry({ uid, name, comment, content, }); } const constantEntry = createWorldbookEntry({ uid: 1, name: "常驻设定", comment: "常驻设定", content: "这里是常驻世界设定。", order: 10, }); const dynEntry = createWorldbookEntry({ uid: 2, name: "EW/Dyn/线索", comment: "线索条目", content: "隐藏线索:<%= charName %> 正在调查。", enabled: false, strategyType: "selective", keys: ["调查"], order: 15, }); const inlineSummaryEntry = createWorldbookEntry({ uid: 3, name: "普通 EJS 汇总", comment: "EJS 汇总", content: '控制摘要:<%= await getwi("EW/Dyn/线索") %>', order: 20, }); const inlineDataSummaryEntry = createWorldbookEntry({ uid: 12, name: "数据 EJS 汇总", comment: "数据 EJS 汇总", content: '数据摘要:<%= await getwi("数据模板", { clue: "蓝钥匙", mood: "紧张" }) %>', order: 21, }); const inlineDataTemplateEntry = createWorldbookEntry({ uid: 13, name: "数据模板", comment: "数据模板", content: "线索=<%= clue %>;情绪=<%= mood %>;角色=<%= char %>;用户=<%= user %>;上下文=<%= recentMessages %>", enabled: false, order: 22, }); const commentKeywordProbeEntry = createWorldbookEntry({ uid: 14, name: "备注命中测试", comment: "常驻备注", content: "这条只用于验证 comment 不参与自定义过滤。", strategyType: "selective", keys: ["绝不会匹配到这里"], order: 23, }); const extensionLiteralEntry = createWorldbookEntry({ uid: 4, name: "扩展语义正文", comment: "扩展语义正文", content: "@@generate\n[GENERATE:Test]\n扩展语义只是普通文本。", order: 25, }); const externalInlineEntry = createWorldbookEntry({ uid: 5, name: "外部书汇总", comment: "外部书汇总", content: '外部补充:<%= await getwi("bonus-book", "Bonus 条目") %>', order: 26, }); const forceControlEntry = createWorldbookEntry({ uid: 6, name: "普通 EJS 控制", comment: "EJS 控制", content: '<% await activewi("强制 after") %>', order: 30, }); const forcedAfterEntry = createWorldbookEntry({ uid: 7, name: "强制 after", comment: "强制后置", content: "这是被 EJS 强制激活的后置条目。", enabled: false, positionType: "after_character_definition", strategyType: "selective", keys: ["永远不会命中"], order: 40, }); const atDepthEntry = createWorldbookEntry({ uid: 8, name: "深度注入", comment: "深度注入", content: "这是一条 atDepth 消息。", positionType: "at_depth_as_system", depth: 2, order: 5, }); const mvuTaggedEntry = createWorldbookEntry({ uid: 9, name: "[mvu_update] 状态同步", comment: "MVU tagged", content: "这一条不应该进入结果。", order: 28, }); const mvuHeuristicEntry = createWorldbookEntry({ uid: 10, name: "MVU 启发式条目", comment: "MVU heuristic", content: "secret=true", order: 29, }); const mvuLazyProbeEntry = createWorldbookEntry({ uid: 11, name: "MVU 懒加载探测", comment: "MVU 懒加载探测", content: 'MVU lazy: <%= await getwi("bonus-book", "Bonus MVU") %>', order: 27, }); const statDataControllerEntry = createWorldbookEntry({ uid: 15, name: "StatData Controller", comment: "StatData Controller", content: '<% if (typeof stat_data !== "undefined" && stat_data?.user?.["\u610f\u8bc6\u72b6\u6001"] === "\u6c89\u7720") { %>stat_data controller payload<% } %>', order: 24, }); const statDataTargetEntry = createWorldbookEntry({ uid: 16, name: "StatData Target", comment: "StatData Target", content: "stat_data controller payload", enabled: false, order: 24.1, }); const messageVarMacroEntry = createWorldbookEntry({ uid: 17, name: "MessageVar Macro", comment: "MessageVar Macro", content: "latest state={{get_message_variable::stat_data.user.\u610f\u8bc6\u72b6\u6001}}", order: 24.2, }); const customContextProbeEntry = createWorldbookEntry({ uid: 18, name: "Custom Context Probe", comment: "Custom Context Probe", content: "上下文探针:user=<%= user_input %>;char=<%= charName %>", strategyType: "selective", keys: ["probe custom mode"], order: 24.3, }); const bonusEntry = createWorldbookEntry({ uid: 101, name: "Bonus 条目", comment: "Bonus 条目", content: "来自 bonus-book 的补充内容。", order: 10, }); const bonusMvuEntry = createWorldbookEntry({ uid: 102, name: "Bonus MVU", comment: "Bonus MVU", content: "变量更新规则:\ntype: sync\n当前时间: 12:00", order: 20, }); const worldbooksByName = { "main-book": [ constantEntry, dynEntry, inlineSummaryEntry, inlineDataSummaryEntry, inlineDataTemplateEntry, commentKeywordProbeEntry, extensionLiteralEntry, externalInlineEntry, mvuLazyProbeEntry, statDataControllerEntry, statDataTargetEntry, messageVarMacroEntry, customContextProbeEntry, forceControlEntry, forcedAfterEntry, atDepthEntry, mvuTaggedEntry, mvuHeuristicEntry, ], "bonus-book": [bonusEntry, bonusMvuEntry], }; try { globalThis.SillyTavern = { getContext() { return { name1: "User", name2: "Alice", chat: [{ is_user: true, mes: "我们继续调查那条线索" }], chatMetadata: {}, extensionSettings: {}, }; }, }; globalThis.getCharWorldbookNames = () => ({ primary: "main-book", additional: [], }); globalThis.getWorldbook = async (worldbookName) => worldbooksByName[worldbookName] || []; globalThis.getLorebookEntries = async (worldbookName) => (worldbooksByName[worldbookName] || []).map((entry) => ({ uid: entry.uid, comment: entry.comment, })); const { resolveTaskWorldInfo } = await import("../prompting/task-worldinfo.js"); const { buildTaskPrompt, buildTaskLlmPayload } = await import( "../prompting/prompt-builder.js" ); const emptyTriggerWorldInfo = await resolveTaskWorldInfo({ chatMessages: [], userMessage: "", templateContext: {}, }); assert.equal( emptyTriggerWorldInfo.beforeEntries.some((entry) => entry.name === "常驻设定"), true, "constant world info should still resolve without trigger text", ); assert.equal( emptyTriggerWorldInfo.beforeEntries.some((entry) => entry.name === "数据 EJS 汇总"), true, "constant EJS entry should still render with empty template context defaults", ); assert.match(emptyTriggerWorldInfo.beforeText, /数据摘要:线索=蓝钥匙;情绪=紧张;角色=Alice;用户=User;上下文=/); assert.equal( emptyTriggerWorldInfo.debug.warnings.some((warning) => warning.includes("渲染失败")), false, ); const worldInfo = await resolveTaskWorldInfo({ templateContext: { recentMessages: "我们继续调查那条线索", charName: "Alice", }, userMessage: "继续调查", }); assert.equal(worldInfo.beforeEntries.length, 6); assert.equal(worldInfo.afterEntries.length, 1); assert.equal(worldInfo.additionalMessages.length, 1); assert.match(worldInfo.additionalMessages[0].content, /atDepth/); assert.match(worldInfo.beforeText, /Alice/); assert.match(worldInfo.beforeText, /bonus-book/); assert.match(worldInfo.beforeText, /MVU lazy:/); assert.match(worldInfo.beforeText, /@@generate/); assert.match(worldInfo.beforeText, /\[GENERATE:Test\]/); assert.doesNotMatch(worldInfo.beforeText, /getwi|<%=?/); assert.doesNotMatch(worldInfo.beforeText, /status_current_variable|updatevariable/i); assert.equal(worldInfo.debug.ejsInlinePullCount, 3); assert.equal(worldInfo.debug.ejsForcedActivationCount, 1); assert.equal(worldInfo.debug.resolvePassCount >= 2, true); assert.equal(worldInfo.debug.forcedActivatedEntries.length, 1); assert.equal(worldInfo.debug.inlinePulledEntries.length, 3); assert.deepEqual(worldInfo.debug.lazyLoadedWorldbooks, ["bonus-book"]); assert.equal(worldInfo.debug.mvu.filteredEntryCount, 3); assert.equal(worldInfo.debug.mvu.lazyFilteredEntryCount, 1); assert.equal(worldInfo.debug.mvu.blockedContentsCount, 4); const defaultFilteredSourceNames = worldInfo.debug.mvu.filteredEntries .map((entry) => entry.sourceName) .sort(); assert.equal(defaultFilteredSourceNames.includes("Bonus MVU"), true); assert.equal(defaultFilteredSourceNames.some((name) => String(name || "").includes("MVU")), true); assert.equal(defaultFilteredSourceNames.some((name) => String(name || "").startsWith("[mvu_update]")), true); assert.equal( worldInfo.debug.warnings.some((warning) => warning.includes("EW/")), true, ); assert.equal( worldInfo.debug.recursionWarnings.some((warning) => warning.includes("mvu filtered world info blocked"), ), true, ); assert.equal(worldInfo.debug.customFilter.mode, "default"); assert.equal(worldInfo.debug.customFilter.filteredEntryCount, 0); globalThis.Mvu = { getMvuData({ type, message_id: messageId } = {}) { if (type === "message" && messageId === "latest") { return { stat_data: { user: { "意识状态": "沉眠", }, "恼恼": { "发情值": 71, }, }, }; } return {}; }, }; const customWorldInfo = await resolveTaskWorldInfo({ settings: { worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "", }, templateContext: { recentMessages: "custom-mode regression probe", charName: "Alice", }, userMessage: "probe custom mode", }); assert.equal( customWorldInfo.beforeEntries.some((entry) => String(entry.sourceName || "").startsWith("[mvu_update]"), ), true, ); assert.equal( customWorldInfo.beforeEntries.some((entry) => String(entry.sourceName || "").includes("MVU"), ), true, ); assert.match( customWorldInfo.beforeText, /secret=true<\/status_current_variable>/, ); assert.match( customWorldInfo.beforeText, /控制摘要:隐藏线索:Alice 正在调查。/, ); assert.match( customWorldInfo.beforeText, /上下文探针:user=probe custom mode;char=Alice/, ); assert.equal( customWorldInfo.allEntries.some((entry) => String(entry.name || "").startsWith("EW/Dyn/")), true, ); assert.equal( customWorldInfo.afterEntries.some((entry) => entry.sourceName === "强制 after"), true, ); assert.equal(customWorldInfo.debug.mvu.filteredEntryCount, 0); assert.equal(customWorldInfo.debug.customFilter.mode, "custom"); assert.equal(customWorldInfo.debug.customFilter.filteredEntryCount, 0); assert.equal( customWorldInfo.debug.customRender.bridgedStatDataFromLatestMessage, true, ); assert.equal(customWorldInfo.debug.customRender.taskEjsStatDataRoots.cache, true); assert.equal( customWorldInfo.debug.customRender.taskEjsStatDataRoots.message, true, ); assert.equal(customWorldInfo.debug.customRender.fallbackEntryCount > 0, true); assert.match(customWorldInfo.beforeText, /stat_data controller payload/); assert.match(customWorldInfo.beforeText, /latest state=.+/); globalThis.EjsTemplate = { async prepareContext() { return { user_input: "OLD_FROM_NATIVE", charName: "OLD_CHAR", }; }, async evalTemplate(text, env) { return String(text) .replace(/<%=\s*user_input\s*%>/g, String(env.user_input ?? "")) .replace(/<%=\s*charName\s*%>/g, String(env.charName ?? "")); }, }; const customWorldInfoWithNativeRuntime = await resolveTaskWorldInfo({ settings: { worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "", }, templateContext: { recentMessages: "custom-mode regression probe", charName: "Alice", }, userMessage: "probe custom mode", }); assert.match( customWorldInfoWithNativeRuntime.beforeText, /上下文探针:user=probe custom mode;char=Alice/, ); assert.doesNotMatch( customWorldInfoWithNativeRuntime.beforeText, /OLD_FROM_NATIVE|OLD_CHAR/, ); delete globalThis.EjsTemplate; const keywordWorldInfo = await resolveTaskWorldInfo({ settings: { worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "常驻", }, templateContext: { recentMessages: "我们继续调查那条线索", charName: "Alice", }, userMessage: "继续调查", }); assert.equal( keywordWorldInfo.beforeEntries.some( (entry) => entry.sourceName === "常驻设定", ), false, ); assert.equal( keywordWorldInfo.allEntries.some((entry) => entry.name === "备注命中测试"), true, ); assert.equal(keywordWorldInfo.debug.customFilter.filteredEntryCount, 1); assert.equal( keywordWorldInfo.debug.customFilter.filteredEntries[0].name, "常驻设定", ); assert.equal( keywordWorldInfo.debug.customFilter.filteredEntries[0].matchedKeyword, "常驻", ); const keywordCachePrime = await resolveTaskWorldInfo({ settings: { worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "常驻,缓存探针", }, templateContext: { recentMessages: "我们继续调查那条线索", charName: "Alice", }, userMessage: "继续调查", }); assert.equal(keywordCachePrime.debug.cache.hit, false); assert.equal(keywordCachePrime.debug.customFilter.filteredEntryCount, 1); const keywordCacheHit = await resolveTaskWorldInfo({ settings: { worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "常驻,缓存探针", }, templateContext: { recentMessages: "我们继续调查那条线索", charName: "Alice", }, userMessage: "继续调查", }); assert.equal(keywordCacheHit.debug.cache.hit, true); assert.equal(keywordCacheHit.debug.customFilter.filteredEntryCount, 1); assert.equal( keywordCacheHit.debug.customFilter.filteredEntries[0].name, "常驻设定", ); delete globalThis.Mvu; const defaultModeWithKeywords = await resolveTaskWorldInfo({ settings: { worldInfoFilterMode: "default", worldInfoFilterCustomKeywords: "常驻", }, templateContext: { recentMessages: "我们继续调查那条线索", charName: "Alice", }, userMessage: "继续调查", }); assert.equal( defaultModeWithKeywords.beforeEntries.some( (entry) => entry.sourceName === "常驻设定", ), true, ); assert.equal(defaultModeWithKeywords.debug.mvu.filteredEntryCount > 0, true); assert.equal(defaultModeWithKeywords.debug.customFilter.filteredEntryCount, 0); const settings = { taskProfiles: { recall: { activeProfileId: "custom", profiles: [ { id: "custom", name: "测试预设", taskType: "recall", builtin: false, blocks: [ { id: "b1", type: "builtin", sourceKey: "worldInfoBefore", role: "system", enabled: true, order: 0, injectionMode: "append", }, { id: "b2", type: "builtin", sourceKey: "worldInfoAfter", role: "system", enabled: true, order: 1, injectionMode: "append", }, { id: "b3", type: "custom", content: "角色: {{charName}}", role: "user", enabled: true, order: 2, injectionMode: "append", }, ], }, ], }, }, }; const promptBuild = await buildTaskPrompt(settings, "recall", { taskName: "recall", userMessage: "继续调查", recentMessages: "我们继续调查那条线索", charName: "Alice", }); assert.match(promptBuild.systemPrompt, /这里是常驻世界设定/); assert.match(promptBuild.systemPrompt, /控制摘要:隐藏线索:Alice 正在调查/); assert.match( promptBuild.systemPrompt, /数据摘要:线索=蓝钥匙;情绪=紧张;角色=Alice;用户=User;上下文=我们继续调查那条线索/, ); assert.match(promptBuild.systemPrompt, /扩展语义只是普通文本/); assert.match(promptBuild.systemPrompt, /来自 bonus-book 的补充内容/); assert.match(promptBuild.systemPrompt, /MVU lazy:/); assert.doesNotMatch(promptBuild.systemPrompt, /getwi|<%=?/); assert.doesNotMatch(promptBuild.systemPrompt, /status_current_variable|变量更新规则|updatevariable/i); assert.equal( promptBuild.privateTaskMessages.length, 2, "custom user block + atDepth world info should both enter private task messages", ); assert.deepEqual( promptBuild.privateTaskMessages.map((message) => message.role), ["system", "user"], ); assert.equal( promptBuild.privateTaskMessages[0].content, "这是一条 atDepth 消息。", ); assert.deepEqual( promptBuild.hostInjections.before.map((entry) => entry.name), [ "常驻设定", "EJS 汇总", "数据 EJS 汇总", "扩展语义正文", "外部书汇总", "MVU 懒加载探测", ], ); assert.deepEqual( promptBuild.hostInjections.after.map((entry) => entry.name), ["强制后置"], ); assert.equal(promptBuild.hostInjections.atDepth.length, 1); assert.equal(promptBuild.hostInjections.atDepth[0].depth, 2); assert.equal(promptBuild.hostInjectionPlan.before.length, 1); assert.equal(promptBuild.hostInjectionPlan.before[0].blockId, "b1"); assert.equal(promptBuild.hostInjectionPlan.before[0].sourceKey, "worldInfoBefore"); assert.deepEqual(promptBuild.hostInjectionPlan.before[0].entryNames, [ "常驻设定", "EJS 汇总", "数据 EJS 汇总", "扩展语义正文", "外部书汇总", "MVU 懒加载探测", ]); assert.equal(promptBuild.hostInjectionPlan.after.length, 1); assert.equal(promptBuild.hostInjectionPlan.after[0].blockId, "b2"); assert.equal(promptBuild.hostInjectionPlan.after[0].sourceKey, "worldInfoAfter"); assert.deepEqual(promptBuild.hostInjectionPlan.after[0].entryNames, ["强制后置"]); assert.equal(promptBuild.hostInjectionPlan.atDepth.length, 1); assert.equal(promptBuild.hostInjectionPlan.atDepth[0].entryName, "深度注入"); assert.equal(typeof promptBuild.debug.worldInfoCacheHit, "boolean"); assert.equal(promptBuild.executionMessages.length, 4); assert.deepEqual( promptBuild.executionMessages.map((message) => message.role), ["system", "system", "system", "user"], ); assert.equal( promptBuild.executionMessages[0].content, "这是一条 atDepth 消息。", ); assert.deepEqual( promptBuild.renderedBlocks.map((block) => block.delivery), ["private.system", "private.system", "private.message"], ); assert.equal(promptBuild.additionalMessages.length, 1); assert.equal(promptBuild.additionalMessages[0].content, "这是一条 atDepth 消息。"); assert.equal(promptBuild.debug.mvu.sanitizedFieldCount >= 0, true); const customPromptBuild = await buildTaskPrompt( { ...settings, worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "", }, "recall", { taskName: "recall", userMessage: "继续调查", recentMessages: "我们继续调查那条线索", charName: "Alice", }, ); assert.match( customPromptBuild.systemPrompt, /secret=true<\/status_current_variable>/, ); assert.match(customPromptBuild.systemPrompt, /这一条不应该进入结果/); assert.match(customPromptBuild.systemPrompt, /控制摘要:隐藏线索:Alice 正在调查/); const customPayload = buildTaskLlmPayload(customPromptBuild, "unused fallback"); assert.equal( customPayload.promptMessages.some((message) => /secret=true<\/status_current_variable>/.test( message.content, ), ), true, ); const interpolatedSettings = { taskProfiles: { recall: { activeProfileId: "interpolated", profiles: [ { id: "interpolated", name: "插值预设", taskType: "recall", builtin: false, blocks: [ { id: "interp-system", type: "custom", content: "世界书插值:\\n{{worldInfoBefore}}", role: "system", enabled: true, order: 0, injectionMode: "append", }, ], }, ], }, }, worldInfoFilterMode: "custom", worldInfoFilterCustomKeywords: "", }; const customInterpolatedPromptBuild = await buildTaskPrompt( interpolatedSettings, "recall", { taskName: "recall", userMessage: "继续调查", recentMessages: "我们继续调查那条线索", charName: "Alice", }, ); assert.match( customInterpolatedPromptBuild.systemPrompt, /secret=true<\/status_current_variable>/, ); const customInterpolatedPayload = buildTaskLlmPayload( customInterpolatedPromptBuild, "unused fallback", ); assert.equal( customInterpolatedPayload.promptMessages.some((message) => /secret=true<\/status_current_variable>/.test( message.content, ), ), true, ); const noWorldInfoBlockSettings = { taskProfiles: { recall: { activeProfileId: "custom", profiles: [ { id: "custom", name: "无世界书显式块", taskType: "recall", builtin: false, blocks: [ { id: "u1", type: "custom", content: "角色: {{charName}}", role: "user", enabled: true, order: 0, injectionMode: "append", }, ], }, ], }, }, }; const atDepthOnlyPromptBuild = await buildTaskPrompt( noWorldInfoBlockSettings, "recall", { taskName: "recall", userMessage: "继续调查", recentMessages: "我们继续调查那条线索", charName: "Alice", }, ); assert.equal(atDepthOnlyPromptBuild.debug.worldInfoRequested, true); assert.equal(atDepthOnlyPromptBuild.debug.worldInfoAtDepthCount, 1); assert.equal(atDepthOnlyPromptBuild.additionalMessages.length, 1); assert.equal( atDepthOnlyPromptBuild.additionalMessages[0].content, "这是一条 atDepth 消息。", ); assert.deepEqual( atDepthOnlyPromptBuild.executionMessages.map((message) => message.role), ["system", "user"], ); assert.equal( atDepthOnlyPromptBuild.executionMessages[0].content, "这是一条 atDepth 消息。", ); const depthD4Entry = createWorldbookEntry({ uid: 201, name: "深度注入 D4", comment: "深度注入 D4", content: "这是 d4 atDepth 消息。", positionType: "at_depth_as_system", depth: 4, order: 8, }); const depthD1Entry = createWorldbookEntry({ uid: 202, name: "深度注入 D1", comment: "深度注入 D1", content: "这是 d1 atDepth 消息。", positionType: "at_depth_as_system", depth: 1, order: 3, }); worldbooksByName["main-book"].push(depthD4Entry, depthD1Entry); const previousGetContext = globalThis.SillyTavern.getContext; globalThis.SillyTavern.getContext = () => ({ ...previousGetContext(), chatId: "depth-aware-chat", }); const depthAwareSettings = { taskProfiles: { recall: { activeProfileId: "depth-aware", profiles: [ { id: "depth-aware", name: "深度顺序预设", taskType: "recall", builtin: false, blocks: [ { id: "depth-recent", type: "builtin", sourceKey: "recentMessages", role: "system", enabled: true, order: 0, injectionMode: "append", }, { id: "depth-user", type: "custom", content: "用户问题:{{userMessage}}", role: "user", enabled: true, order: 1, injectionMode: "append", }, ], }, ], }, }, }; const depthAwarePromptBuild = await buildTaskPrompt(depthAwareSettings, "recall", { taskName: "recall", userMessage: "继续调查 depth 排序", recentMessages: "这里会被 chatMessages 替换", chatMessages: [ { seq: 11, role: "user", content: "第一句" }, { seq: 12, role: "assistant", content: "第二句" }, ], charName: "Alice", }); assert.deepEqual( depthAwarePromptBuild.executionMessages.map((message) => message.content), [ "#1 [assistant|深度注入 D4]: 这是 d4 atDepth 消息。\n\n#2 [assistant|深度注入]: 这是一条 atDepth 消息。\n\n#11 [user]: 第一句\n\n#4 [assistant|深度注入 D1]: 这是 d1 atDepth 消息。\n\n#12 [assistant]: 第二句", "用户问题:继续调查 depth 排序", ], ); assert.deepEqual( depthAwarePromptBuild.hostInjections.atDepth.map((entry) => entry.name), ["深度注入 D4", "深度注入", "深度注入 D1"], ); assert.deepEqual( depthAwarePromptBuild.hostInjectionPlan.atDepth.map((entry) => entry.entryName), ["深度注入 D4", "深度注入", "深度注入 D1"], ); assert.equal( depthAwarePromptBuild.executionMessages.at(-1)?.content.includes("atDepth"), false, ); worldbooksByName["main-book"].splice(-2, 2); globalThis.SillyTavern.getContext = previousGetContext; const { initializeHostAdapter } = await import("../host/adapter/index.js"); const partialBridgeCalls = []; const partialBridgeEntriesByWorldbook = { "main-book": [createConstantWorldbookEntry(11, "主书原名", "主书内容。", "主书注释")], "side-book": [createConstantWorldbookEntry(12, "支线原名", "支线内容。", "支线注释")], "persona-book": [createConstantWorldbookEntry(13, "人格原名", "人格内容。", "人格注释")], "chat-book": [createConstantWorldbookEntry(14, "聊天原名", "聊天内容。", "聊天注释")], }; globalThis.SillyTavern = { getContext() { return { name1: "User", name2: "Alice", chat: [{ is_user: true, mes: "我们继续调查那条线索" }], chatMetadata: { world: "chat-book", }, extensionSettings: { persona_description_lorebook: "persona-book", }, }; }, }; globalThis.getCharWorldbookNames = () => ({ primary: "main-book", additional: ["side-book"], }); globalThis.getWorldbook = async () => { throw new Error( "legacy getWorldbook should not be used when bridge getWorldbook is available", ); }; globalThis.getLorebookEntries = async (worldbookName) => (partialBridgeEntriesByWorldbook[worldbookName] || []).map((entry) => ({ uid: entry.uid, comment: entry.comment, })); initializeHostAdapter({ worldbookProvider: { async getWorldbook(worldbookName) { partialBridgeCalls.push(worldbookName); return partialBridgeEntriesByWorldbook[worldbookName] || []; }, }, }); const partialBridgeWorldInfo = await resolveTaskWorldInfo({ templateContext: { recentMessages: "我们继续调查那条线索", charName: "Alice", }, userMessage: "继续调查", }); assert.deepEqual(partialBridgeCalls, [ "main-book", "side-book", "persona-book", "chat-book", ]); assert.deepEqual( partialBridgeWorldInfo.beforeEntries.map((entry) => entry.name).sort(), ["主书注释", "支线注释", "人格注释", "聊天注释"].sort(), ); console.log("task-worldinfo tests passed"); } finally { if (originalSillyTavern === undefined) { delete globalThis.SillyTavern; } else { globalThis.SillyTavern = originalSillyTavern; } if (originalEjsTemplate === undefined) { delete globalThis.EjsTemplate; } else { globalThis.EjsTemplate = originalEjsTemplate; } if (originalMvu === undefined) { delete globalThis.Mvu; } else { globalThis.Mvu = originalMvu; } if (originalGetCharWorldbookNames === undefined) { delete globalThis.getCharWorldbookNames; } else { globalThis.getCharWorldbookNames = originalGetCharWorldbookNames; } if (originalGetWorldbook === undefined) { delete globalThis.getWorldbook; } else { globalThis.getWorldbook = originalGetWorldbook; } if (originalGetLorebookEntries === undefined) { delete globalThis.getLorebookEntries; } else { globalThis.getLorebookEntries = originalGetLorebookEntries; } try { const { initializeHostAdapter } = await import("../host/adapter/index.js"); initializeHostAdapter({}); } catch { // ignore reset failures in test cleanup } }