Merge branch 'main' into main

This commit is contained in:
Hao19911125
2026-04-09 22:54:42 +08:00
committed by GitHub
4 changed files with 322 additions and 43 deletions

View File

@@ -6,6 +6,6 @@
"js": "index.js",
"css": "style.css",
"author": "Youzini",
"version": "4.2.3",
"version": "4.2.2",
"homePage": "https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology"
}

View File

@@ -269,6 +269,14 @@ function messageUsesWorldInfoContent(message = {}) {
return String(message?.source || "") === "worldInfo-atDepth";
}
function getOptionalFiniteNumber(value) {
if (value === null || value === undefined || value === "") {
return null;
}
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : null;
}
function getPromptMessageLikeDescriptor(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
@@ -279,7 +287,7 @@ function getPromptMessageLikeDescriptor(value) {
return {
content: String(value.content || ""),
role: role === "user" ? "user" : "assistant",
seq: Number.isFinite(Number(value.seq)) ? Number(value.seq) : null,
seq: getOptionalFiniteNumber(value.seq),
};
}
@@ -287,7 +295,7 @@ function getPromptMessageLikeDescriptor(value) {
return {
content: String(value.mes || ""),
role: value.is_user === true ? "user" : "assistant",
seq: Number.isFinite(Number(value.seq)) ? Number(value.seq) : null,
seq: getOptionalFiniteNumber(value.seq),
};
}
@@ -1072,6 +1080,41 @@ function sortInjectionEntries(entries = []) {
});
}
function sortAtDepthInjectionEntries(entries = []) {
return [...entries].sort((left, right) => {
const depthLeft = Number.isFinite(Number(left?.depth))
? Number(left.depth)
: 0;
const depthRight = Number.isFinite(Number(right?.depth))
? Number(right.depth)
: 0;
const orderLeft = Number.isFinite(Number(left?.order))
? Number(left.order)
: 0;
const orderRight = Number.isFinite(Number(right?.order))
? Number(right.order)
: 0;
const uidLeft = Number.isFinite(Number(left?.uid))
? Number(left.uid)
: Number.NEGATIVE_INFINITY;
const uidRight = Number.isFinite(Number(right?.uid))
? Number(right.uid)
: Number.NEGATIVE_INFINITY;
const indexLeft = Number.isFinite(Number(left?.index))
? Number(left.index)
: 0;
const indexRight = Number.isFinite(Number(right?.index))
? Number(right.index)
: 0;
return (
depthRight - depthLeft ||
orderLeft - orderRight ||
uidRight - uidLeft ||
indexLeft - indexRight
);
});
}
function createHostInjectionPlanEntry(block = {}, position, extra = {}) {
return {
source: "block",
@@ -1154,10 +1197,98 @@ function buildHostInjectionPlan(renderedBlocks = [], worldInfoResolution = {}) {
return {
before: sortInjectionEntries(plan.before),
after: sortInjectionEntries(plan.after),
atDepth: sortInjectionEntries(plan.atDepth),
atDepth: sortAtDepthInjectionEntries(plan.atDepth),
};
}
function createInjectedAtDepthChatMessage(message = {}) {
const descriptor = getPromptMessageLikeDescriptor(message);
if (!descriptor) {
return null;
}
return {
...(message && typeof message === "object" ? message : {}),
role: descriptor.role,
content: descriptor.content,
seq: descriptor.seq,
uid: Number.isFinite(Number(message?.uid))
? Number(message.uid)
: null,
index: Number.isFinite(Number(message?.index))
? Number(message.index)
: null,
name: String(message?.name || ""),
sourceName: String(message?.sourceName || ""),
worldbook: String(message?.worldbook || ""),
source: String(message?.source || "worldInfo-atDepth"),
sourceKey: String(message?.sourceKey || "taskAdditionalMessages"),
derivedFromWorldInfo: true,
contentOrigin:
String(message?.contentOrigin || "") ||
PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
sanitizationEligible: message?.sanitizationEligible === true,
regexSourceType: String(message?.regexSourceType || "world_info"),
depth: Number.isFinite(Number(message?.depth))
? Number(message.depth)
: 0,
order: Number.isFinite(Number(message?.order))
? Number(message.order)
: 0,
};
}
function injectAtDepthMessagesIntoChatMessages(
chatMessages = [],
atDepthMessages = [],
) {
const normalizedChatMessages = (Array.isArray(chatMessages) ? chatMessages : [])
.map((message) => {
const descriptor = getPromptMessageLikeDescriptor(message);
if (!descriptor) return null;
return {
...(message && typeof message === "object" ? message : {}),
role: descriptor.role,
content: descriptor.content,
seq: descriptor.seq,
};
})
.filter(Boolean);
if (normalizedChatMessages.length === 0) {
return null;
}
const groupedByDepth = new Map();
for (const message of sortAtDepthInjectionEntries(atDepthMessages)) {
const injectedMessage = createInjectedAtDepthChatMessage(message);
if (!injectedMessage) continue;
const depth = Math.max(0, Number(injectedMessage.depth || 0));
if (!groupedByDepth.has(depth)) {
groupedByDepth.set(depth, []);
}
groupedByDepth.get(depth).push(injectedMessage);
}
if (groupedByDepth.size === 0) {
return normalizedChatMessages;
}
const reversedMessages = [...normalizedChatMessages].reverse();
const sortedDepths = [...groupedByDepth.keys()].sort((left, right) => left - right);
let totalInsertedMessages = 0;
for (const depth of sortedDepths) {
const depthMessages = groupedByDepth.get(depth) || [];
if (depthMessages.length === 0) continue;
const injectIndex = Math.min(
Math.max(0, depth + totalInsertedMessages),
reversedMessages.length,
);
reversedMessages.splice(injectIndex, 0, ...depthMessages);
totalInsertedMessages += depthMessages.length;
}
return reversedMessages.reverse();
}
function getPromptFieldContentOrigin(sourceKey = "") {
const normalizedSourceKey = String(sourceKey || "").trim();
if (!normalizedSourceKey) {
@@ -1303,6 +1434,7 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
const emptyWorldInfo = buildEmptyWorldInfoContext();
let resolvedWorldInfo = emptyWorldInfo;
let worldInfoRuntimeBlockedContents = [];
let deliveredAtDepthViaChatMessages = false;
if (worldInfoRequested) {
const worldInfo = await resolveTaskWorldInfo({
@@ -1333,6 +1465,28 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
taskAdditionalMessages: sanitizedWorldInfo.additionalMessages || [],
worldInfoDebug: sanitizedWorldInfo.debug || null,
};
if (
Array.isArray(sanitizedInputContext.chatMessages) &&
isPromptMessageArray(sanitizedInputContext.chatMessages)
) {
const injectedChatMessages = injectAtDepthMessagesIntoChatMessages(
sanitizedInputContext.chatMessages,
sanitizedWorldInfo.additionalMessages,
);
if (Array.isArray(injectedChatMessages) && injectedChatMessages.length > 0) {
sanitizedInputContext.chatMessages = injectedChatMessages;
if (typeof context.recentMessages === "string") {
sanitizedInputContext.recentMessages =
stringifyInterpolatedValue(injectedChatMessages);
}
if (typeof context.dialogueText === "string") {
sanitizedInputContext.dialogueText =
stringifyInterpolatedValue(injectedChatMessages);
}
deliveredAtDepthViaChatMessages = true;
}
}
}
const resolvedContext = {
@@ -1479,36 +1633,44 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
}
}
const atDepthExecutionMessages = (worldInfoResolution.additionalMessages || [])
.map((message) =>
createExecutionMessage(
message.role,
message.content,
{
source: "worldInfo-atDepth",
sourceKey: "taskAdditionalMessages",
contentOrigin:
String(message.contentOrigin || "") ||
PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
sanitizationEligible: message.sanitizationEligible === true,
regexSourceType: String(message.regexSourceType || "world_info"),
depth: Number.isFinite(Number(message?.depth))
? Number(message.depth)
: null,
order: Number.isFinite(Number(message?.order))
? Number(message.order)
: 0,
},
),
)
.filter(Boolean);
const finalExecutionMessages = deliveredAtDepthViaChatMessages
? executionMessages
: [...atDepthExecutionMessages, ...executionMessages];
const privateTaskMessages = deliveredAtDepthViaChatMessages
? [...customMessages]
: [...worldInfoResolution.additionalMessages, ...customMessages];
debugLog(
`[ST-BME][prompt-diag] buildTaskPrompt done: ` +
`executionMessages=${executionMessages.length}, ` +
`executionMessages=${finalExecutionMessages.length}, ` +
`userBlocks=${userRoleBlockCount}, systemBlocks=${systemRoleBlockCount}, ` +
`customMessages=${customMessages.length}`,
`customMessages=${customMessages.length}, ` +
`atDepthMessages=${atDepthExecutionMessages.length}, ` +
`atDepthViaChatMessages=${deliveredAtDepthViaChatMessages}`,
);
for (const message of worldInfoResolution.additionalMessages || []) {
const executionMessage = createExecutionMessage(
message.role,
message.content,
{
source: "worldInfo-atDepth",
sourceKey: "taskAdditionalMessages",
contentOrigin:
String(message.contentOrigin || "") ||
PROMPT_CONTENT_ORIGIN.WORLD_INFO_RENDERED,
sanitizationEligible: message.sanitizationEligible === true,
regexSourceType: String(message.regexSourceType || "world_info"),
},
);
if (executionMessage) {
executionMessages.push(executionMessage);
}
}
const privateTaskMessages = [
...customMessages,
...worldInfoResolution.additionalMessages,
];
const hostInjectionPlan = buildHostInjectionPlan(
renderedBlocks,
worldInfoResolution,
@@ -1522,7 +1684,7 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
systemPrompt,
messages: privateTaskMessages,
},
executionMessages,
executionMessages: finalExecutionMessages,
privateTaskMessages,
renderedBlocks,
regexInput: mergeRegexCollectors(promptRegexInput),
@@ -1562,14 +1724,16 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
customMessageCount: customMessages.length,
additionalMessageCount: worldInfoResolution.additionalMessages.length,
privateTaskMessageCount: privateTaskMessages.length,
executionMessageCount: executionMessages.length,
executionMessageCount: finalExecutionMessages.length,
userRoleBlockCount,
assistantRoleBlockCount,
systemRoleBlockCount,
effectiveDelivery: {
profileBlocks: "ordered-private-messages",
worldInfoBeforeAfter: "inline-in-ordered-messages",
worldInfoAtDepth: "appended-private-messages",
worldInfoAtDepth: deliveredAtDepthViaChatMessages
? "inserted-into-chat-messages-by-depth"
: "appended-private-messages-fallback",
},
worldInfoCacheHit: Boolean(worldInfoResolution.debug?.cache?.hit),
ejsRuntimeStatus: worldInfoResolution.debug?.ejsRuntimeStatus || "",
@@ -1639,7 +1803,7 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
profileName: profile?.name || "",
systemPrompt,
privateTaskMessages,
executionMessages,
executionMessages: finalExecutionMessages,
renderedBlocks,
hostInjections: worldInfoResolution.injections,
hostInjectionPlan,

View File

@@ -1158,6 +1158,7 @@ function normalizeResolvedEntry(entry = {}, fallbackIndex = 0) {
? entry.role
: "system";
return {
uid: Number(entry.uid ?? 0),
name: normalizeKey(entry.name),
sourceName: normalizeKey(
entry.sourceName || entry.source_name || entry.name,
@@ -1185,6 +1186,7 @@ function sortAtDepthEntries(entries = []) {
return (
depthB - depthA ||
(a.order ?? 100) - (b.order ?? 100) ||
(b.uid ?? 0) - (a.uid ?? 0) ||
a.index - b.index
);
});
@@ -1195,6 +1197,13 @@ function buildAdditionalMessages(entries = []) {
.map((entry) => ({
role: entry.role,
content: String(entry.content || "").trim(),
depth: Number(entry.depth ?? 0),
order: Number(entry.order ?? 100),
uid: Number(entry.uid ?? 0),
index: Number(entry.index ?? 0),
name: String(entry.name || ""),
sourceName: String(entry.sourceName || entry.name || ""),
worldbook: String(entry.worldbook || ""),
source: "worldInfo-atDepth",
sourceKey: "taskAdditionalMessages",
}))

View File

@@ -265,7 +265,7 @@ const bonusEntry = createWorldbookEntry({
order: 10,
});
const bonusMvuEntry = createWorldbookEntry({
const bonusMvuEntry = createWorldbookEntry({
uid: 102,
name: "Bonus MVU",
comment: "Bonus MVU",
@@ -273,8 +273,8 @@ const bonusMvuEntry = createWorldbookEntry({
order: 20,
});
const worldbooksByName = {
"main-book": [
const worldbooksByName = {
"main-book": [
constantEntry,
dynEntry,
inlineSummaryEntry,
@@ -293,9 +293,9 @@ const worldbooksByName = {
atDepthEntry,
mvuTaggedEntry,
mvuHeuristicEntry,
],
"bonus-book": [bonusEntry, bonusMvuEntry],
};
],
"bonus-book": [bonusEntry, bonusMvuEntry],
};
try {
globalThis.SillyTavern = {
@@ -663,7 +663,11 @@ try {
);
assert.deepEqual(
promptBuild.privateTaskMessages.map((message) => message.role),
["user", "system"],
["system", "user"],
);
assert.equal(
promptBuild.privateTaskMessages[0].content,
"这是一条 atDepth 消息。",
);
assert.deepEqual(
promptBuild.hostInjections.before.map((entry) => entry.name),
@@ -703,7 +707,11 @@ try {
assert.equal(promptBuild.executionMessages.length, 4);
assert.deepEqual(
promptBuild.executionMessages.map((message) => message.role),
["system", "system", "user", "system"],
["system", "system", "system", "user"],
);
assert.equal(
promptBuild.executionMessages[0].content,
"这是一条 atDepth 消息。",
);
assert.deepEqual(
promptBuild.renderedBlocks.map((block) => block.delivery),
@@ -845,8 +853,106 @@ try {
);
assert.deepEqual(
atDepthOnlyPromptBuild.executionMessages.map((message) => message.role),
["user", "system"],
["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 atDepth 消息。\n\n#2 [assistant]: 这是一条 atDepth 消息。\n\n#11 [user]: 第一句\n\n#4 [assistant]: 这是 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 = [];