Fix task EJS runtime context and getwi compatibility

This commit is contained in:
Youzini-afk
2026-04-04 21:22:42 +08:00
parent 5d11a736ee
commit ae3a8d264f
3 changed files with 344 additions and 66 deletions

View File

@@ -139,10 +139,32 @@ function buildTemplateContext(templateContext = {}, hostSnapshot) {
: snapshot.chat.lastUserMessage || ""; : snapshot.chat.lastUserMessage || "";
return { return {
userMessage: "",
recentMessages: "",
chatMessages: [],
dialogueText: "",
candidateText: "",
candidateNodes: [],
nodeContent: "",
eventSummary: "",
characterSummary: "",
threadSummary: "",
contradictionSummary: "",
graphStats: "",
schema: "",
currentRange: "",
worldInfoBefore: "",
worldInfoAfter: "",
worldInfoBeforeEntries: [],
worldInfoAfterEntries: [],
worldInfoAtDepthEntries: [],
activatedWorldInfoNames: [],
taskAdditionalMessages: [],
user: snapshot.user.name, user: snapshot.user.name,
char: snapshot.character.name, char: snapshot.character.name,
userName: promptAliases.userName || snapshot.user.name, userName: promptAliases.userName || snapshot.user.name,
charName: promptAliases.charName || snapshot.character.name, charName: promptAliases.charName || snapshot.character.name,
assistantName: promptAliases.charName || snapshot.character.name,
persona: promptAliases.userPersona || snapshot.persona.text, persona: promptAliases.userPersona || snapshot.persona.text,
userPersona: promptAliases.userPersona || snapshot.persona.text, userPersona: promptAliases.userPersona || snapshot.persona.text,
charDescription: charDescription:
@@ -181,6 +203,17 @@ function cloneDeep(value) {
} }
} }
function isPlainObject(value) {
if (!value || typeof value !== "object") {
return false;
}
if (Array.isArray(value)) {
return false;
}
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function getByPath(target, path, defaultValue = undefined) { function getByPath(target, path, defaultValue = undefined) {
const result = String(path || "") const result = String(path || "")
.split(".") .split(".")
@@ -197,6 +230,60 @@ function normalizeEntryKey(value) {
return String(value ?? "").trim(); return String(value ?? "").trim();
} }
function isEntryIdentifier(value) {
return (
typeof value === "string" ||
typeof value === "number" ||
value instanceof RegExp
);
}
function cloneRegExp(pattern) {
return new RegExp(pattern.source, pattern.flags);
}
function matchesWorldbookIdentifier(worldbook, identifier) {
if (!isEntryIdentifier(identifier)) {
return false;
}
if (identifier instanceof RegExp) {
return cloneRegExp(identifier).test(String(worldbook || ""));
}
return normalizeEntryKey(worldbook) === normalizeEntryKey(identifier);
}
function matchesEntryIdentifier(entry = {}, identifier) {
if (!isEntryIdentifier(identifier)) {
return false;
}
const entryName = normalizeEntryKey(entry.name);
const entryComment = normalizeEntryKey(entry.comment);
const entryUid = Number(entry.uid) || 0;
if (identifier instanceof RegExp) {
const pattern = cloneRegExp(identifier);
return pattern.test(entryComment) || pattern.test(entryName);
}
if (typeof identifier === "number") {
return entryUid === identifier;
}
const normalizedIdentifier = normalizeEntryKey(identifier);
if (!normalizedIdentifier) {
return false;
}
return (
entryComment === normalizedIdentifier ||
entryName === normalizedIdentifier ||
String(entryUid) === normalizedIdentifier
);
}
function normalizeIdentifier(value) { function normalizeIdentifier(value) {
return String(value || "") return String(value || "")
.trim() .trim()
@@ -310,6 +397,7 @@ function registerEntries(renderCtx, entries = []) {
renderCtx.entries.push(entry); renderCtx.entries.push(entry);
registerEntryLookup(renderCtx.allEntries, entry.name, entry); registerEntryLookup(renderCtx.allEntries, entry.name, entry);
registerEntryLookup(renderCtx.allEntries, entry.comment, entry); registerEntryLookup(renderCtx.allEntries, entry.comment, entry);
registerEntryLookup(renderCtx.allEntries, entry.uid, entry);
if (!renderCtx.entriesByWorldbook.has(entry.worldbook)) { if (!renderCtx.entriesByWorldbook.has(entry.worldbook)) {
renderCtx.entriesByWorldbook.set(entry.worldbook, new Map()); renderCtx.entriesByWorldbook.set(entry.worldbook, new Map());
@@ -317,6 +405,7 @@ function registerEntries(renderCtx, entries = []) {
const worldbookLookup = renderCtx.entriesByWorldbook.get(entry.worldbook); const worldbookLookup = renderCtx.entriesByWorldbook.get(entry.worldbook);
registerEntryLookup(worldbookLookup, entry.name, entry); registerEntryLookup(worldbookLookup, entry.name, entry);
registerEntryLookup(worldbookLookup, entry.comment, entry); registerEntryLookup(worldbookLookup, entry.comment, entry);
registerEntryLookup(worldbookLookup, entry.uid, entry);
} }
} }
@@ -372,53 +461,151 @@ async function ensureWorldbookEntriesLoaded(renderCtx, worldbookName) {
return renderCtx.entriesByWorldbook.has(normalizedWorldbook); return renderCtx.entriesByWorldbook.has(normalizedWorldbook);
} }
async function resolveEntry(renderCtx, currentWorldbook, worldbookOrEntry, entryNameOrData) { function lookupEntryInMap(lookup, identifier) {
const explicitWorldbook = if (!(lookup instanceof Map) || !isEntryIdentifier(identifier)) {
typeof entryNameOrData === "string" return undefined;
? normalizeEntryKey(worldbookOrEntry)
: "";
const fallbackWorldbook = normalizeEntryKey(currentWorldbook);
const identifier = normalizeEntryKey(
typeof entryNameOrData === "string" ? entryNameOrData : worldbookOrEntry,
);
if (!identifier) return undefined;
const lookupInWorldbook = (worldbook) => {
if (!worldbook) return undefined;
return renderCtx.entriesByWorldbook.get(worldbook)?.get(identifier);
};
let resolved =
lookupInWorldbook(explicitWorldbook) ||
lookupInWorldbook(fallbackWorldbook) ||
renderCtx.allEntries.get(identifier);
if (!resolved && explicitWorldbook) {
await ensureWorldbookEntriesLoaded(renderCtx, explicitWorldbook);
resolved =
lookupInWorldbook(explicitWorldbook) ||
lookupInWorldbook(fallbackWorldbook) ||
renderCtx.allEntries.get(identifier);
} }
if ( if (!(identifier instanceof RegExp)) {
!resolved && const direct = lookup.get(normalizeEntryKey(identifier));
typeof renderCtx.resolveIgnoredEntry === "function" if (direct) {
) { return direct;
const ignoredEntry =
renderCtx.resolveIgnoredEntry(explicitWorldbook || fallbackWorldbook, identifier) ||
renderCtx.resolveIgnoredEntry("", identifier);
if (ignoredEntry) {
const descriptor = ignoredEntry.sourceName || ignoredEntry.name || identifier;
recordRenderWarning(
renderCtx,
`mvu filtered world info blocked: ${ignoredEntry.worldbook ? `${ignoredEntry.worldbook}/` : ""}${descriptor}`,
);
} }
} }
return resolved; for (const entry of lookup.values()) {
if (matchesEntryIdentifier(entry, identifier)) {
return entry;
}
}
return undefined;
}
function buildCandidateLookups(renderCtx, currentWorldbook, explicitWorldbook = null) {
const candidates = [];
const seen = new Set();
const pushLookup = (lookup) => {
if (!(lookup instanceof Map) || seen.has(lookup)) {
return;
}
seen.add(lookup);
candidates.push(lookup);
};
if (typeof explicitWorldbook === "string") {
pushLookup(renderCtx.entriesByWorldbook.get(normalizeEntryKey(explicitWorldbook)));
} else if (explicitWorldbook instanceof RegExp) {
for (const [worldbookName, lookup] of renderCtx.entriesByWorldbook.entries()) {
if (matchesWorldbookIdentifier(worldbookName, explicitWorldbook)) {
pushLookup(lookup);
}
}
}
const fallbackWorldbook = normalizeEntryKey(currentWorldbook);
if (fallbackWorldbook) {
pushLookup(renderCtx.entriesByWorldbook.get(fallbackWorldbook));
}
pushLookup(renderCtx.allEntries);
return candidates;
}
async function resolveEntry(renderCtx, currentWorldbook, worldbookOrEntry, entryNameOrData) {
const hasExplicitWorldbook = isEntryIdentifier(entryNameOrData);
const explicitWorldbook = hasExplicitWorldbook ? worldbookOrEntry : null;
const fallbackWorldbook = normalizeEntryKey(currentWorldbook);
const identifier = hasExplicitWorldbook ? entryNameOrData : worldbookOrEntry;
if (!isEntryIdentifier(identifier)) {
return undefined;
}
const directLookups = buildCandidateLookups(
renderCtx,
fallbackWorldbook,
explicitWorldbook,
);
for (const lookup of directLookups) {
const matched = lookupEntryInMap(lookup, identifier);
if (matched) {
return matched;
}
}
if (typeof explicitWorldbook === "string" && normalizeEntryKey(explicitWorldbook)) {
await ensureWorldbookEntriesLoaded(renderCtx, explicitWorldbook);
const loadedLookups = buildCandidateLookups(
renderCtx,
fallbackWorldbook,
explicitWorldbook,
);
for (const lookup of loadedLookups) {
const matched = lookupEntryInMap(lookup, identifier);
if (matched) {
return matched;
}
}
}
if (!renderCtx.resolveIgnoredEntry || identifier instanceof RegExp) {
return undefined;
}
const normalizedIdentifier = normalizeEntryKey(identifier);
const explicitWorldbookName =
typeof explicitWorldbook === "string" ? normalizeEntryKey(explicitWorldbook) : "";
const ignoredEntry =
renderCtx.resolveIgnoredEntry(
explicitWorldbookName || fallbackWorldbook,
normalizedIdentifier,
) || renderCtx.resolveIgnoredEntry("", normalizedIdentifier);
if (ignoredEntry) {
const descriptor = ignoredEntry.sourceName || ignoredEntry.name || normalizedIdentifier;
recordRenderWarning(
renderCtx,
`mvu filtered world info blocked: ${ignoredEntry.worldbook ? `${ignoredEntry.worldbook}/` : ""}${descriptor}`,
);
}
return undefined;
}
function parseActivateWorldInfoArgs(world, entryOrForce, maybeForce) {
const hasExplicitWorldbook = isEntryIdentifier(entryOrForce);
return {
explicitWorldbook: hasExplicitWorldbook ? world : null,
identifier: hasExplicitWorldbook ? entryOrForce : world,
force:
typeof maybeForce === "boolean"
? maybeForce
: typeof entryOrForce === "boolean",
};
}
function parseGetwiArgs(worldbookOrEntry, entryNameOrData, dataOrUndefined) {
const hasExplicitWorldbook = isEntryIdentifier(entryNameOrData);
return {
explicitWorldbook: hasExplicitWorldbook ? worldbookOrEntry : null,
identifier: hasExplicitWorldbook ? entryNameOrData : worldbookOrEntry,
data: isPlainObject(hasExplicitWorldbook ? dataOrUndefined : entryNameOrData)
? cloneDeep(hasExplicitWorldbook ? dataOrUndefined : entryNameOrData)
: {},
};
}
function mergeEjsExtraEnv(...values) {
const utilityLib = getUtilityLib();
const merge = typeof utilityLib?.merge === "function" ? utilityLib.merge : null;
const plainValues = values.filter((value) => isPlainObject(value));
if (plainValues.length === 0) {
return {};
}
if (merge) {
return merge({}, ...plainValues.map((value) => cloneDeep(value)));
}
return Object.assign({}, ...plainValues.map((value) => ({ ...value })));
} }
async function activateWorldInfoInContext( async function activateWorldInfoInContext(
@@ -428,20 +615,28 @@ async function activateWorldInfoInContext(
entryOrForce, entryOrForce,
maybeForce, maybeForce,
) { ) {
const hasExplicitWorldbook = typeof entryOrForce === "string"; const parsed = parseActivateWorldInfoArgs(world, entryOrForce, maybeForce);
const identifier = normalizeEntryKey(hasExplicitWorldbook ? entryOrForce : world); const identifierLabel =
const explicitWorldbook = hasExplicitWorldbook ? normalizeEntryKey(world) : ""; parsed.identifier instanceof RegExp
? parsed.identifier.toString()
: normalizeEntryKey(parsed.identifier);
const explicitWorldbookLabel =
typeof parsed.explicitWorldbook === "string"
? normalizeEntryKey(parsed.explicitWorldbook)
: parsed.explicitWorldbook instanceof RegExp
? parsed.explicitWorldbook.toString()
: "";
const entry = await resolveEntry( const entry = await resolveEntry(
renderCtx, renderCtx,
currentWorldbook, currentWorldbook,
explicitWorldbook || identifier, parsed.explicitWorldbook,
hasExplicitWorldbook ? identifier : undefined, parsed.identifier,
); );
if (!entry) { if (!entry) {
recordRenderWarning( recordRenderWarning(
renderCtx, renderCtx,
`activewi target not found: ${explicitWorldbook ? `${explicitWorldbook}/` : ""}${identifier}`, `activewi target not found: ${explicitWorldbookLabel ? `${explicitWorldbookLabel}/` : ""}${identifierLabel}`,
); );
return null; return null;
} }
@@ -456,7 +651,7 @@ async function activateWorldInfoInContext(
world: normalizedEntry.worldbook, world: normalizedEntry.worldbook,
comment: normalizedEntry.comment || normalizedEntry.name, comment: normalizedEntry.comment || normalizedEntry.name,
content: normalizedEntry.content, content: normalizedEntry.content,
forced: typeof maybeForce === "boolean" ? maybeForce : typeof entryOrForce === "boolean", forced: parsed.force,
}; };
} }
@@ -465,12 +660,18 @@ async function getwi(
currentWorldbook, currentWorldbook,
worldbookOrEntry, worldbookOrEntry,
entryNameOrData, entryNameOrData,
dataOrUndefined,
) { ) {
const parsed = parseGetwiArgs(
worldbookOrEntry,
entryNameOrData,
dataOrUndefined,
);
const entry = await resolveEntry( const entry = await resolveEntry(
renderCtx, renderCtx,
currentWorldbook, currentWorldbook,
worldbookOrEntry, parsed.explicitWorldbook,
entryNameOrData, parsed.identifier,
); );
if (!entry) { if (!entry) {
return ""; return "";
@@ -508,6 +709,7 @@ async function getwi(
renderCtx.renderStack.add(entryKey); renderCtx.renderStack.add(entryKey);
try { try {
finalContent = await evalTaskEjsTemplate(processed, renderCtx, { finalContent = await evalTaskEjsTemplate(processed, renderCtx, {
...mergeEjsExtraEnv(parsed.data),
world_info: { world_info: {
comment: entry.comment || entry.name, comment: entry.comment || entry.name,
name: entry.name, name: entry.name,
@@ -677,6 +879,7 @@ export async function evalTaskEjsTemplate(content, renderCtx, extraEnv = {}) {
const hostSnapshot = resolveHostSnapshot(renderCtx?.hostSnapshot); const hostSnapshot = resolveHostSnapshot(renderCtx?.hostSnapshot);
const snapshot = hostSnapshot.snapshot; const snapshot = hostSnapshot.snapshot;
const templateAliases = buildTemplateContext(renderCtx?.templateContext || {}, hostSnapshot);
const processed = substituteTaskEjsParams(content, renderCtx?.templateContext, { const processed = substituteTaskEjsParams(content, renderCtx?.templateContext, {
hostSnapshot, hostSnapshot,
}); });
@@ -695,6 +898,7 @@ export async function evalTaskEjsTemplate(content, renderCtx, extraEnv = {}) {
const stCtx = snapshot.raw || {}; const stCtx = snapshot.raw || {};
const chat = snapshot.chat.messages || []; const chat = snapshot.chat.messages || [];
const utilityLib = getUtilityLib(); const utilityLib = getUtilityLib();
const templateRuntimeEnv = mergeEjsExtraEnv(templateAliases);
const workflowUserInput = const workflowUserInput =
typeof renderCtx?.templateContext?.user_input === "string" typeof renderCtx?.templateContext?.user_input === "string"
? renderCtx.templateContext.user_input ? renderCtx.templateContext.user_input
@@ -735,12 +939,21 @@ export async function evalTaskEjsTemplate(content, renderCtx, extraEnv = {}) {
const context = { const context = {
_: utilityLib, _: utilityLib,
console, console,
userName: snapshot.user.name, ...templateRuntimeEnv,
charName: snapshot.character.name, user: templateAliases.user,
assistantName: snapshot.character.name, char: templateAliases.char,
charDescription: snapshot.character.description || "", persona:
userPersona: snapshot.persona.text || "", templateAliases.persona || templateAliases.userPersona || snapshot.persona.text || "",
currentTime: snapshot.time.current || "", userName: templateAliases.userName || snapshot.user.name,
charName: templateAliases.charName || snapshot.character.name,
assistantName:
templateAliases.assistantName ||
templateAliases.charName ||
snapshot.character.name,
charDescription:
templateAliases.charDescription || snapshot.character.description || "",
userPersona: templateAliases.userPersona || snapshot.persona.text || "",
currentTime: templateAliases.currentTime || snapshot.time.current || "",
characterId: snapshot.character.id, characterId: snapshot.character.id,
hostSnapshot: snapshot, hostSnapshot: snapshot,
stSnapshot: snapshot, stSnapshot: snapshot,
@@ -818,19 +1031,21 @@ export async function evalTaskEjsTemplate(content, renderCtx, extraEnv = {}) {
get SillyTavern() { get SillyTavern() {
return stCtx; return stCtx;
}, },
getwi: (worldbookOrEntry, entryNameOrData) => getwi: (worldbookOrEntry, entryNameOrData, dataOrUndefined) =>
getwi( getwi(
renderCtx, renderCtx,
String(context.world_info?.world || ""), String(context.world_info?.world || ""),
worldbookOrEntry, worldbookOrEntry,
entryNameOrData, entryNameOrData,
dataOrUndefined,
), ),
getWorldInfo: (worldbookOrEntry, entryNameOrData) => getWorldInfo: (worldbookOrEntry, entryNameOrData, dataOrUndefined) =>
getwi( getwi(
renderCtx, renderCtx,
String(context.world_info?.world || ""), String(context.world_info?.world || ""),
worldbookOrEntry, worldbookOrEntry,
entryNameOrData, entryNameOrData,
dataOrUndefined,
), ),
getvar: (path, options) => getVariable(renderCtx.variableState, path, options), getvar: (path, options) => getVariable(renderCtx.variableState, path, options),
getLocalVar: (path, options = {}) => getLocalVar: (path, options = {}) =>

View File

@@ -144,6 +144,8 @@ try {
locals.variables.score, locals.variables.score,
locals.variables.location, locals.variables.location,
locals.lastUserMessage, locals.lastUserMessage,
locals.recentMessages,
locals.persona,
locals.hostSnapshot.character.worldbook, locals.hostSnapshot.character.worldbook,
locals.stSnapshot.chat.lastUserMessage, locals.stSnapshot.chat.lastUserMessage,
typeof locals.execute, typeof locals.execute,
@@ -154,7 +156,14 @@ try {
const renderCtx = createTaskEjsRenderContext([], { const renderCtx = createTaskEjsRenderContext([], {
hostSnapshot, hostSnapshot,
templateContext: {}, templateContext: {
user: "AliasUser",
char: "AliasAlice",
userName: "AliasUser",
charName: "AliasAlice",
recentMessages: "最近上下文",
persona: "AliasPersona",
},
}); });
const primaryBackend = await inspectTaskEjsRuntimeBackend({ const primaryBackend = await inspectTaskEjsRuntimeBackend({
ensureRuntime: false, ensureRuntime: false,
@@ -169,7 +178,7 @@ try {
const rendered = await evalTaskEjsTemplate("<%= 1 %>", renderCtx); const rendered = await evalTaskEjsTemplate("<%= 1 %>", renderCtx);
assert.equal( assert.equal(
rendered, rendered,
"Alice|User|persona-book|chat-book|7|library|最后一句|char-book|最后一句|function", "AliasAlice|AliasUser|persona-book|chat-book|7|library|最后一句|最近上下文|AliasPersona|char-book|最后一句|function",
); );
assert.deepEqual(compileCalls, ["<%= 1 %>", "<%= 1 %>"]); assert.deepEqual(compileCalls, ["<%= 1 %>", "<%= 1 %>"]);
@@ -192,7 +201,7 @@ try {
assert.equal(failedBackend.isFallback, false); assert.equal(failedBackend.isFallback, false);
const passthrough = await evalTaskEjsTemplate("{{charName}}", renderCtx); const passthrough = await evalTaskEjsTemplate("{{charName}}", renderCtx);
assert.equal(passthrough, "Alice"); assert.equal(passthrough, "AliasAlice");
} finally { } finally {
globalThis.SillyTavern = originalSillyTavern; globalThis.SillyTavern = originalSillyTavern;
globalThis.getCurrentChatId = originalGetCurrentChatId; globalThis.getCurrentChatId = originalGetCurrentChatId;

View File

@@ -103,6 +103,25 @@ const inlineSummaryEntry = createWorldbookEntry({
order: 20, 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 extensionLiteralEntry = createWorldbookEntry({ const extensionLiteralEntry = createWorldbookEntry({
uid: 4, uid: 4,
name: "扩展语义正文", name: "扩展语义正文",
@@ -193,6 +212,8 @@ const worldbooksByName = {
constantEntry, constantEntry,
dynEntry, dynEntry,
inlineSummaryEntry, inlineSummaryEntry,
inlineDataSummaryEntry,
inlineDataTemplateEntry,
extensionLiteralEntry, extensionLiteralEntry,
externalInlineEntry, externalInlineEntry,
mvuLazyProbeEntry, mvuLazyProbeEntry,
@@ -242,6 +263,16 @@ try {
true, true,
"constant world info should still resolve without trigger text", "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({ const worldInfo = await resolveTaskWorldInfo({
templateContext: { templateContext: {
@@ -253,19 +284,30 @@ try {
assert.deepEqual( assert.deepEqual(
worldInfo.beforeEntries.map((entry) => entry.name), worldInfo.beforeEntries.map((entry) => entry.name),
["常驻设定", "EJS 汇总", "扩展语义正文", "外部书汇总", "MVU 懒加载探测"], [
"常驻设定",
"EJS 汇总",
"数据 EJS 汇总",
"扩展语义正文",
"外部书汇总",
"MVU 懒加载探测",
],
); );
assert.deepEqual(worldInfo.afterEntries.map((entry) => entry.name), ["强制后置"]); assert.deepEqual(worldInfo.afterEntries.map((entry) => entry.name), ["强制后置"]);
assert.equal(worldInfo.additionalMessages.length, 1); assert.equal(worldInfo.additionalMessages.length, 1);
assert.equal(worldInfo.additionalMessages[0].content, "这是一条 atDepth 消息。"); assert.equal(worldInfo.additionalMessages[0].content, "这是一条 atDepth 消息。");
assert.match(worldInfo.beforeText, /控制摘要隐藏线索Alice 正在调查。/); assert.match(worldInfo.beforeText, /控制摘要隐藏线索Alice 正在调查。/);
assert.match(
worldInfo.beforeText,
/数据摘要:线索=蓝钥匙;情绪=紧张;角色=Alice用户=User上下文=我们继续调查那条线索/,
);
assert.match(worldInfo.beforeText, /外部补充:来自 bonus-book 的补充内容。/); assert.match(worldInfo.beforeText, /外部补充:来自 bonus-book 的补充内容。/);
assert.match(worldInfo.beforeText, /MVU lazy:/); assert.match(worldInfo.beforeText, /MVU lazy:/);
assert.match(worldInfo.beforeText, /@@generate/); assert.match(worldInfo.beforeText, /@@generate/);
assert.match(worldInfo.beforeText, /\[GENERATE:Test\]/); assert.match(worldInfo.beforeText, /\[GENERATE:Test\]/);
assert.doesNotMatch(worldInfo.beforeText, /getwi|<%=?/); assert.doesNotMatch(worldInfo.beforeText, /getwi|<%=?/);
assert.doesNotMatch(worldInfo.beforeText, /status_current_variable|变量更新规则|updatevariable/i); assert.doesNotMatch(worldInfo.beforeText, /status_current_variable|变量更新规则|updatevariable/i);
assert.equal(worldInfo.debug.ejsInlinePullCount, 2); assert.equal(worldInfo.debug.ejsInlinePullCount, 3);
assert.equal(worldInfo.debug.ejsForcedActivationCount, 1); assert.equal(worldInfo.debug.ejsForcedActivationCount, 1);
assert.equal(worldInfo.debug.resolvePassCount >= 2, true); assert.equal(worldInfo.debug.resolvePassCount >= 2, true);
assert.deepEqual(worldInfo.debug.forcedActivatedEntries.map((entry) => entry.name), [ assert.deepEqual(worldInfo.debug.forcedActivatedEntries.map((entry) => entry.name), [
@@ -273,7 +315,7 @@ try {
]); ]);
assert.deepEqual( assert.deepEqual(
worldInfo.debug.inlinePulledEntries.map((entry) => entry.name).sort(), worldInfo.debug.inlinePulledEntries.map((entry) => entry.name).sort(),
["Bonus 条目", "线索条目"].sort(), ["Bonus 条目", "数据模板", "线索条目"].sort(),
); );
assert.deepEqual(worldInfo.debug.lazyLoadedWorldbooks, ["bonus-book"]); assert.deepEqual(worldInfo.debug.lazyLoadedWorldbooks, ["bonus-book"]);
assert.equal(worldInfo.debug.mvu.filteredEntryCount, 2); assert.equal(worldInfo.debug.mvu.filteredEntryCount, 2);
@@ -348,6 +390,10 @@ try {
assert.match(promptBuild.systemPrompt, /这里是常驻世界设定/); assert.match(promptBuild.systemPrompt, /这里是常驻世界设定/);
assert.match(promptBuild.systemPrompt, /控制摘要隐藏线索Alice 正在调查/); assert.match(promptBuild.systemPrompt, /控制摘要隐藏线索Alice 正在调查/);
assert.match(
promptBuild.systemPrompt,
/数据摘要:线索=蓝钥匙;情绪=紧张;角色=Alice用户=User上下文=我们继续调查那条线索/,
);
assert.match(promptBuild.systemPrompt, /扩展语义只是普通文本/); assert.match(promptBuild.systemPrompt, /扩展语义只是普通文本/);
assert.match(promptBuild.systemPrompt, /来自 bonus-book 的补充内容/); assert.match(promptBuild.systemPrompt, /来自 bonus-book 的补充内容/);
assert.match(promptBuild.systemPrompt, /MVU lazy:/); assert.match(promptBuild.systemPrompt, /MVU lazy:/);
@@ -364,7 +410,14 @@ try {
); );
assert.deepEqual( assert.deepEqual(
promptBuild.hostInjections.before.map((entry) => entry.name), promptBuild.hostInjections.before.map((entry) => entry.name),
["常驻设定", "EJS 汇总", "扩展语义正文", "外部书汇总", "MVU 懒加载探测"], [
"常驻设定",
"EJS 汇总",
"数据 EJS 汇总",
"扩展语义正文",
"外部书汇总",
"MVU 懒加载探测",
],
); );
assert.deepEqual( assert.deepEqual(
promptBuild.hostInjections.after.map((entry) => entry.name), promptBuild.hostInjections.after.map((entry) => entry.name),
@@ -378,6 +431,7 @@ try {
assert.deepEqual(promptBuild.hostInjectionPlan.before[0].entryNames, [ assert.deepEqual(promptBuild.hostInjectionPlan.before[0].entryNames, [
"常驻设定", "常驻设定",
"EJS 汇总", "EJS 汇总",
"数据 EJS 汇总",
"扩展语义正文", "扩展语义正文",
"外部书汇总", "外部书汇总",
"MVU 懒加载探测", "MVU 懒加载探测",