Fix custom worldinfo EJS resolution

This commit is contained in:
Youzini-afk
2026-04-07 20:26:18 +08:00
parent 27bdc1f9eb
commit ed534aeb37
3 changed files with 81 additions and 18 deletions

View File

@@ -117,7 +117,7 @@ function resolveFormatMessageVariableMacros(text, messageVars) {
export async function renderTemplateWithStSupport(
text,
{ env = null, messageVars = null } = {},
{ env = null, messageVars = null, evaluateEjs = true } = {},
) {
const originalText = String(text ?? "");
const runtime = getTemplateRuntime();
@@ -131,7 +131,7 @@ export async function renderTemplateWithStSupport(
let ejsEvaluated = false;
let ejsError = null;
if (originalText.includes("<%")) {
if (evaluateEjs && originalText.includes("<%")) {
try {
const evalTemplate =
runtime?.evalTemplate || runtime?.evaltemplate || null;

View File

@@ -907,9 +907,6 @@ async function loadNormalizedWorldbookEntries(
normalizedName,
);
if (String(filterMode || "default") === "custom") {
if (!normalizedEntry.enabled) {
continue;
}
if (Array.isArray(customFilterKeywords) && customFilterKeywords.length > 0) {
const nameLower = normalizedEntry.name.toLowerCase();
const matchedKeyword = customFilterKeywords.find((keyword) =>
@@ -1631,6 +1628,7 @@ export async function resolveTaskWorldInfo({
const sourceContent = entry.cleanContent || entry.content;
let renderedContent = sourceContent;
let taskEjsRenderedContent = sourceContent;
let taskEjsError = null;
try {
taskEjsRenderedContent = await evalTaskEjsTemplate(sourceContent, renderCtx, {
world_info: {
@@ -1660,26 +1658,34 @@ export async function resolveTaskWorldInfo({
result.debug.ejsLastError =
error instanceof Error ? error.message : String(error);
}
taskEjsError = error;
taskEjsRenderedContent = "";
}
renderedContent = taskEjsRenderedContent;
if (isCustomFilter) {
const stNativeRender = await renderTemplateWithStSupport(sourceContent, {
env: customRenderEnv,
messageVars: customRenderMessageVars,
});
const sourceIncludesEjs = String(sourceContent || "").includes("<%");
const shouldAttemptNativeEjsFallback =
taskEjsError?.code === "st_bme_task_ejs_runtime_unavailable" &&
sourceIncludesEjs;
const stNativeRender = await renderTemplateWithStSupport(
shouldAttemptNativeEjsFallback ? sourceContent : renderedContent,
{
env: customRenderEnv,
messageVars: customRenderMessageVars,
evaluateEjs: shouldAttemptNativeEjsFallback,
},
);
if (stNativeRender.ejsError) {
result.debug.customRender.ejsErrorCount += 1;
}
const sourceIncludesEjs = String(sourceContent || "").includes("<%");
const shouldUseStNativeResult =
(!sourceIncludesEjs &&
(shouldAttemptNativeEjsFallback && stNativeRender.ejsEvaluated) ||
(!shouldAttemptNativeEjsFallback &&
(stNativeRender.macroApplied ||
stNativeRender.messageVariableMacrosApplied ||
stNativeRender.text !== sourceContent)) ||
(sourceIncludesEjs && stNativeRender.ejsEvaluated);
stNativeRender.text !== renderedContent));
if (shouldUseStNativeResult) {
renderedContent = stNativeRender.text;

View File

@@ -177,6 +177,7 @@ const forcedAfterEntry = createWorldbookEntry({
name: "强制 after",
comment: "强制后置",
content: "这是被 EJS 强制激活的后置条目。",
enabled: false,
positionType: "after_character_definition",
strategyType: "selective",
keys: ["永远不会命中"],
@@ -242,6 +243,16 @@ const messageVarMacroEntry = createWorldbookEntry({
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 条目",
@@ -272,6 +283,7 @@ const worldbooksByName = {
statDataControllerEntry,
statDataTargetEntry,
messageVarMacroEntry,
customContextProbeEntry,
forceControlEntry,
forcedAfterEntry,
atDepthEntry,
@@ -423,9 +435,21 @@ try {
customWorldInfo.beforeText,
/<status_current_variable>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/")),
false,
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");
@@ -443,6 +467,42 @@ try {
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",
@@ -668,10 +728,7 @@ try {
/<status_current_variable>secret=true<\/status_current_variable>/,
);
assert.match(customPromptBuild.systemPrompt, /这一条不应该进入结果/);
assert.doesNotMatch(
customPromptBuild.systemPrompt,
/控制摘要隐藏线索Alice 正在调查/,
);
assert.match(customPromptBuild.systemPrompt, /控制摘要隐藏线索Alice 正在调查/);
const customPayload = buildTaskLlmPayload(customPromptBuild, "unused fallback");
assert.equal(
customPayload.promptMessages.some((message) =>