import assert from "node:assert/strict";
import { registerHooks } from "node:module";
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,
)}`;
registerHooks({
resolve(specifier, context, nextResolve) {
if (
specifier === "../../../extensions.js" ||
specifier === "../../../../extensions.js" ||
specifier === "../../../../../extensions.js"
) {
return {
shortCircuit: true,
url: extensionsShimUrl,
};
}
if (
specifier === "../../../../script.js" ||
specifier === "../../../../../script.js"
) {
return {
shortCircuit: true,
url: scriptShimUrl,
};
}
return nextResolve(specifier, context);
},
});
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),
["user", "system"],
);
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", "user", "system"],
);
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),
["user", "system"],
);
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
}
}