mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Fix task EJS runtime context and getwi compatibility
This commit is contained in:
333
task-ejs.js
333
task-ejs.js
@@ -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 = {}) =>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 懒加载探测",
|
||||||
|
|||||||
Reference in New Issue
Block a user