mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
feat: integrate phase3/4 settings UI and add phase4/5 regressions
This commit is contained in:
387
tests/extractor-phase5-context-fidelity.mjs
Normal file
387
tests/extractor-phase5-context-fidelity.mjs
Normal file
@@ -0,0 +1,387 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
installResolveHooks,
|
||||
toDataModuleUrl,
|
||||
} from "./helpers/register-hooks-compat.mjs";
|
||||
|
||||
const extensionsShimSource = [
|
||||
"export const extension_settings = {};",
|
||||
"export function getContext() {",
|
||||
" return globalThis.__stBmeTestContext || {",
|
||||
" chat: [],",
|
||||
" chatMetadata: {},",
|
||||
" extensionSettings: {},",
|
||||
" powerUserSettings: {},",
|
||||
" characters: {},",
|
||||
" characterId: null,",
|
||||
" name1: '玩家',",
|
||||
" name2: '艾琳',",
|
||||
" chatId: 'test-chat',",
|
||||
" };",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
const scriptShimSource = [
|
||||
"export function getRequestHeaders() {",
|
||||
" return {};",
|
||||
"}",
|
||||
"export function substituteParamsExtended(value) {",
|
||||
" return String(value ?? '');",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
const openAiShimSource = [
|
||||
"export const chat_completion_sources = {};",
|
||||
"export async function sendOpenAIRequest() {",
|
||||
" throw new Error('sendOpenAIRequest should not be called in phase5 fidelity test');",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
installResolveHooks([
|
||||
{
|
||||
specifiers: [
|
||||
"../../../extensions.js",
|
||||
"../../../../extensions.js",
|
||||
"../../../../../extensions.js",
|
||||
],
|
||||
url: toDataModuleUrl(extensionsShimSource),
|
||||
},
|
||||
{
|
||||
specifiers: [
|
||||
"../../../../script.js",
|
||||
"../../../../../script.js",
|
||||
],
|
||||
url: toDataModuleUrl(scriptShimSource),
|
||||
},
|
||||
{
|
||||
specifiers: [
|
||||
"../../../../openai.js",
|
||||
"../../../../../openai.js",
|
||||
],
|
||||
url: toDataModuleUrl(openAiShimSource),
|
||||
},
|
||||
]);
|
||||
|
||||
const { createEmptyGraph } = await import("../graph/graph.js");
|
||||
const { DEFAULT_NODE_SCHEMA } = await import("../graph/schema.js");
|
||||
const { extractMemories } = await import("../maintenance/extractor.js");
|
||||
const { defaultSettings } = await import("../runtime/settings-defaults.js");
|
||||
|
||||
function setTestOverrides(overrides = {}) {
|
||||
globalThis.__stBmeTestOverrides = overrides;
|
||||
return () => {
|
||||
delete globalThis.__stBmeTestOverrides;
|
||||
};
|
||||
}
|
||||
|
||||
function collectAllPromptContent(captured) {
|
||||
return [
|
||||
String(captured.systemPrompt || ""),
|
||||
String(captured.userPrompt || ""),
|
||||
...(Array.isArray(captured.promptMessages) ? captured.promptMessages : []).map(
|
||||
(message) => String(message.content || ""),
|
||||
),
|
||||
...(Array.isArray(captured.additionalMessages)
|
||||
? captured.additionalMessages
|
||||
: []
|
||||
).map((message) => String(message.content || "")),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function createWorldbookEntry({
|
||||
uid,
|
||||
name,
|
||||
comment = name,
|
||||
content,
|
||||
enabled = true,
|
||||
keys = [],
|
||||
positionType = "before_character_definition",
|
||||
role = "system",
|
||||
depth = 0,
|
||||
order = 10,
|
||||
strategyType = keys.length > 0 ? "selective" : "constant",
|
||||
}) {
|
||||
return {
|
||||
uid,
|
||||
name,
|
||||
comment,
|
||||
content,
|
||||
enabled,
|
||||
position: {
|
||||
type: positionType,
|
||||
role,
|
||||
depth,
|
||||
order,
|
||||
},
|
||||
strategy: {
|
||||
type: strategyType,
|
||||
keys,
|
||||
keys_secondary: { logic: "and_any", keys: [] },
|
||||
},
|
||||
probability: 100,
|
||||
extra: {},
|
||||
};
|
||||
}
|
||||
|
||||
const originalSillyTavern = globalThis.SillyTavern;
|
||||
const originalGetCharWorldbookNames = globalThis.getCharWorldbookNames;
|
||||
const originalGetWorldbook = globalThis.getWorldbook;
|
||||
const originalGetLorebookEntries = globalThis.getLorebookEntries;
|
||||
const originalTestContext = globalThis.__stBmeTestContext;
|
||||
|
||||
const worldbooksByName = {
|
||||
"main-book": [
|
||||
createWorldbookEntry({
|
||||
uid: 1,
|
||||
name: "主书常驻设定",
|
||||
content: "主世界书:蓝钥匙线索。",
|
||||
order: 10,
|
||||
}),
|
||||
createWorldbookEntry({
|
||||
uid: 2,
|
||||
name: "蓝钥匙触发条目",
|
||||
content: "主世界书命中:调查蓝钥匙时应关注旧城区。",
|
||||
keys: ["蓝钥匙"],
|
||||
order: 20,
|
||||
}),
|
||||
],
|
||||
"persona-book": [
|
||||
createWorldbookEntry({
|
||||
uid: 3,
|
||||
name: "人格设定",
|
||||
content: "人格世界书:保持谨慎,不要忽略路线细节。",
|
||||
order: 10,
|
||||
}),
|
||||
],
|
||||
"chat-book": [
|
||||
createWorldbookEntry({
|
||||
uid: 4,
|
||||
name: "聊天绑定设定",
|
||||
content: "聊天世界书:当前会话已锁定旧城区雨夜调查。",
|
||||
order: 10,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
const fidelityMessages = [
|
||||
{
|
||||
seq: 30,
|
||||
role: "assistant",
|
||||
content: "<think>先推断</think><action>举灯</action>艾琳说:去调查蓝钥匙。",
|
||||
name: "艾琳",
|
||||
speaker: "艾琳",
|
||||
},
|
||||
{
|
||||
seq: 31,
|
||||
role: "assistant",
|
||||
content: "旁白补充:<status mood='tense'>雨夜</status>巷子很安静。",
|
||||
name: "旁白",
|
||||
speaker: "旁白",
|
||||
},
|
||||
{
|
||||
seq: 32,
|
||||
role: "user",
|
||||
content: "<plan>先记路线</plan>我会继续调查蓝钥匙。",
|
||||
name: "玩家",
|
||||
speaker: "玩家",
|
||||
},
|
||||
];
|
||||
|
||||
globalThis.__stBmeTestContext = {
|
||||
chat: [
|
||||
{ is_user: false, mes: "艾琳说:去调查蓝钥匙。", name: "艾琳" },
|
||||
{ is_user: false, mes: "旁白补充:雨夜巷子很安静。", name: "旁白" },
|
||||
{ is_user: true, mes: "我会继续调查蓝钥匙。", name: "玩家" },
|
||||
],
|
||||
chatMetadata: {
|
||||
world: "chat-book",
|
||||
},
|
||||
extensionSettings: {
|
||||
persona_description_lorebook: "persona-book",
|
||||
},
|
||||
powerUserSettings: {
|
||||
persona_description: "用户设定:谨慎调查者",
|
||||
},
|
||||
characters: {
|
||||
1: {
|
||||
name: "艾琳",
|
||||
description: "角色描述:夜巡调查员",
|
||||
data: {
|
||||
description: "角色描述:夜巡调查员",
|
||||
extensions: {
|
||||
world: "main-book",
|
||||
},
|
||||
},
|
||||
extensions: {
|
||||
world: "main-book",
|
||||
},
|
||||
},
|
||||
},
|
||||
characterId: 1,
|
||||
name1: "玩家",
|
||||
name2: "艾琳",
|
||||
chatId: "phase5-context-fidelity",
|
||||
};
|
||||
|
||||
globalThis.SillyTavern = {
|
||||
getContext() {
|
||||
return globalThis.__stBmeTestContext;
|
||||
},
|
||||
};
|
||||
|
||||
globalThis.getCharWorldbookNames = () => ({
|
||||
primary: "main-book",
|
||||
additional: [],
|
||||
});
|
||||
globalThis.getWorldbook = async (worldbookName) =>
|
||||
worldbooksByName[String(worldbookName || "").trim()] || [];
|
||||
globalThis.getLorebookEntries = async (worldbookName) =>
|
||||
(worldbooksByName[String(worldbookName || "").trim()] || []).map((entry) => ({
|
||||
uid: entry.uid,
|
||||
comment: entry.comment,
|
||||
}));
|
||||
|
||||
try {
|
||||
{
|
||||
const graph = createEmptyGraph();
|
||||
let captured = null;
|
||||
const restore = setTestOverrides({
|
||||
llm: {
|
||||
async callLLMForJSON(payload) {
|
||||
captured = payload;
|
||||
return { operations: [], cognitionUpdates: [], regionUpdates: {} };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await extractMemories({
|
||||
graph,
|
||||
messages: fidelityMessages,
|
||||
startSeq: 30,
|
||||
endSeq: 32,
|
||||
schema: DEFAULT_NODE_SCHEMA,
|
||||
embeddingConfig: null,
|
||||
settings: {
|
||||
...defaultSettings,
|
||||
extractAssistantExcludeTags: "think,action",
|
||||
extractWorldbookMode: "active",
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.success, true);
|
||||
assert.ok(captured);
|
||||
|
||||
const allContent = collectAllPromptContent(captured);
|
||||
assert.match(allContent, /角色描述:夜巡调查员/);
|
||||
assert.match(allContent, /用户设定:谨慎调查者/);
|
||||
assert.match(allContent, /主世界书:蓝钥匙线索。/);
|
||||
assert.match(allContent, /主世界书命中:调查蓝钥匙时应关注旧城区。/);
|
||||
assert.match(allContent, /人格世界书:保持谨慎,不要忽略路线细节。/);
|
||||
assert.match(allContent, /聊天世界书:当前会话已锁定旧城区雨夜调查。/);
|
||||
|
||||
const recentBlock = (Array.isArray(captured.promptMessages)
|
||||
? captured.promptMessages
|
||||
: []
|
||||
).find((message) => message.sourceKey === "recentMessages");
|
||||
assert.ok(recentBlock, "recentMessages block should exist");
|
||||
const recentContent = String(recentBlock?.content || "");
|
||||
assert.match(recentContent, /#30 \[assistant\|艾琳\]: 艾琳说:去调查蓝钥匙。/);
|
||||
assert.match(
|
||||
recentContent,
|
||||
/#31 \[assistant\|旁白\]: 旁白补充:<status mood='tense'>雨夜<\/status>巷子很安静。/,
|
||||
);
|
||||
assert.match(
|
||||
recentContent,
|
||||
/#32 \[user\|玩家\]: <plan>先记路线<\/plan>我会继续调查蓝钥匙。/,
|
||||
);
|
||||
assert.doesNotMatch(recentContent, /<think>|<action>/);
|
||||
|
||||
const worldInfoBeforeBlock = (Array.isArray(captured.promptMessages)
|
||||
? captured.promptMessages
|
||||
: []
|
||||
).find((message) => message.sourceKey === "worldInfoBefore");
|
||||
assert.ok(worldInfoBeforeBlock, "worldInfoBefore block should exist when worldbook is active");
|
||||
assert.match(String(worldInfoBeforeBlock?.content || ""), /蓝钥匙线索/);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const graph = createEmptyGraph();
|
||||
let captured = null;
|
||||
const restore = setTestOverrides({
|
||||
llm: {
|
||||
async callLLMForJSON(payload) {
|
||||
captured = payload;
|
||||
return { operations: [], cognitionUpdates: [], regionUpdates: {} };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await extractMemories({
|
||||
graph,
|
||||
messages: fidelityMessages,
|
||||
startSeq: 30,
|
||||
endSeq: 32,
|
||||
schema: DEFAULT_NODE_SCHEMA,
|
||||
embeddingConfig: null,
|
||||
settings: {
|
||||
...defaultSettings,
|
||||
extractAssistantExcludeTags: "think,action",
|
||||
extractWorldbookMode: "none",
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.success, true);
|
||||
assert.ok(captured);
|
||||
|
||||
const allContent = collectAllPromptContent(captured);
|
||||
assert.match(allContent, /角色描述:夜巡调查员/);
|
||||
assert.match(allContent, /用户设定:谨慎调查者/);
|
||||
assert.doesNotMatch(allContent, /主世界书:蓝钥匙线索。/);
|
||||
assert.doesNotMatch(allContent, /主世界书命中:调查蓝钥匙时应关注旧城区。/);
|
||||
assert.doesNotMatch(allContent, /人格世界书:保持谨慎,不要忽略路线细节。/);
|
||||
assert.doesNotMatch(allContent, /聊天世界书:当前会话已锁定旧城区雨夜调查。/);
|
||||
|
||||
const recentBlock = (Array.isArray(captured.promptMessages)
|
||||
? captured.promptMessages
|
||||
: []
|
||||
).find((message) => message.sourceKey === "recentMessages");
|
||||
assert.ok(recentBlock, "recentMessages block should still exist when worldbook is disabled");
|
||||
assert.match(String(recentBlock?.content || ""), /#30 \[assistant\|艾琳\]: 艾琳说:去调查蓝钥匙。/);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (originalSillyTavern === undefined) {
|
||||
delete globalThis.SillyTavern;
|
||||
} else {
|
||||
globalThis.SillyTavern = originalSillyTavern;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (originalTestContext === undefined) {
|
||||
delete globalThis.__stBmeTestContext;
|
||||
} else {
|
||||
globalThis.__stBmeTestContext = originalTestContext;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("extractor-phase5-context-fidelity tests passed");
|
||||
@@ -248,10 +248,11 @@ export function createGenerationRecallHarness(options = {}) {
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
`${snippet}\nresult = { hashRecallInput, buildPreGenerationRecallKey, buildGenerationAfterCommandsRecallInput, buildNormalGenerationRecallInput, cleanupGenerationRecallTransactions, buildGenerationRecallTransactionId, beginGenerationRecallTransaction, markGenerationRecallTransactionHookState, shouldRunRecallForTransaction, createGenerationRecallContext, onGenerationStarted, onGenerationEnded, onGenerationAfterCommands, onBeforeCombinePrompts, applyFinalRecallInjectionForGeneration, ensurePersistedRecallRecordForGeneration, findRecentGenerationRecallTransactionForChat, getGenerationRecallTransactionResult, generationRecallTransactions, freezeHostGenerationInputSnapshot, consumeHostGenerationInputSnapshot, getPendingHostGenerationInputSnapshot, clearPendingHostGenerationInputSnapshot, recordRecallSendIntent, clearPendingRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage, getCurrentGenerationTrivialSkip, markCurrentGenerationTrivialSkip, clearCurrentGenerationTrivialSkip, consumeCurrentGenerationTrivialSkip, deferAutoExtraction, maybeResumePendingAutoExtraction, clearPendingAutoExtraction, getPendingAutoExtraction: () => ({ ...pendingAutoExtraction }), getIsHostGenerationRunning: () => isHostGenerationRunning, runPlannerRecallForEna, getGraphPersistenceState: () => graphPersistenceState, setGraphPersistenceState: (value = {}) => { graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; return graphPersistenceState; } };`,
|
||||
`${snippet}\nresult = { hashRecallInput, buildPreGenerationRecallKey, buildGenerationAfterCommandsRecallInput, buildNormalGenerationRecallInput, cleanupGenerationRecallTransactions, buildGenerationRecallTransactionId, beginGenerationRecallTransaction, markGenerationRecallTransactionHookState, shouldRunRecallForTransaction, createGenerationRecallContext, onGenerationStarted, onGenerationEnded, onGenerationAfterCommands, onBeforeCombinePrompts, applyFinalRecallInjectionForGeneration, ensurePersistedRecallRecordForGeneration, findRecentGenerationRecallTransactionForChat, getGenerationRecallTransactionResult, generationRecallTransactions, freezeHostGenerationInputSnapshot, consumeHostGenerationInputSnapshot, getPendingHostGenerationInputSnapshot, clearPendingHostGenerationInputSnapshot, recordRecallSendIntent, clearPendingRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage, getCurrentGenerationTrivialSkip, markCurrentGenerationTrivialSkip, clearCurrentGenerationTrivialSkip, consumeCurrentGenerationTrivialSkip, deferAutoExtraction, maybeResumePendingAutoExtraction, clearPendingAutoExtraction, getPendingAutoExtraction: () => ({ ...pendingAutoExtraction }), getIsHostGenerationRunning: () => isHostGenerationRunning, preparePlannerRecallHandoff, runPlannerRecallForEna, getGraphPersistenceState: () => graphPersistenceState, setGraphPersistenceState: (value = {}) => { graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; return graphPersistenceState; } };`,
|
||||
context,
|
||||
{ filename: indexPath },
|
||||
);
|
||||
|
||||
Object.defineProperties(context, {
|
||||
pendingRecallSendIntent: {
|
||||
get() {
|
||||
|
||||
@@ -41,6 +41,120 @@ async function testSendIntentCanRemainAuthoritativeQueryWhenFlagEnabled() {
|
||||
assert.equal(transaction.frozenRecallOptions.includeSyntheticUserMessage, true);
|
||||
}
|
||||
|
||||
async function testPlannerHandoffCanRemainAuthoritativeQueryWhenFlagEnabled() {
|
||||
const harness = await createGenerationRecallHarness();
|
||||
harness.extension_settings[MODULE_NAME] = {
|
||||
recallUseAuthoritativeGenerationInput: true,
|
||||
};
|
||||
harness.chat = [{ is_user: true, mes: "楼层里的稳定用户输入" }];
|
||||
|
||||
const handoff = harness.result.preparePlannerRecallHandoff({
|
||||
rawUserInput: "planner 原始输入",
|
||||
plannerAugmentedMessage: "planner 增强后的输入",
|
||||
plannerRecall: {
|
||||
memoryBlock: "规划记忆块",
|
||||
recentMessages: ["[user]: planner 原始输入", "[assistant]: 记忆命中"],
|
||||
result: {
|
||||
selectedNodeIds: ["node-planner-1"],
|
||||
stats: {
|
||||
coreCount: 1,
|
||||
recallCount: 1,
|
||||
},
|
||||
meta: {
|
||||
retrieval: {
|
||||
vectorHits: 1,
|
||||
vectorMergedHits: 0,
|
||||
diffusionHits: 0,
|
||||
candidatePoolAfterDpp: 1,
|
||||
llm: {
|
||||
status: "disabled",
|
||||
candidatePool: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
chatId: "chat-main",
|
||||
});
|
||||
|
||||
assert.ok(handoff);
|
||||
|
||||
const recallContext = harness.result.createGenerationRecallContext({
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
generationType: "normal",
|
||||
recallOptions: {},
|
||||
chatId: "chat-main",
|
||||
});
|
||||
|
||||
assert.equal(recallContext.shouldRun, true);
|
||||
assert.equal(recallContext.recallOptions.overrideUserMessage, "planner 原始输入");
|
||||
assert.equal(recallContext.recallOptions.overrideSource, "planner-handoff");
|
||||
assert.equal(recallContext.recallOptions.authoritativeInputUsed, true);
|
||||
assert.equal(
|
||||
recallContext.recallOptions.boundUserFloorText,
|
||||
"楼层里的稳定用户输入",
|
||||
);
|
||||
assert.equal(recallContext.recallOptions.includeSyntheticUserMessage, true);
|
||||
assert.ok(recallContext.recallOptions.cachedRecallPayload);
|
||||
assert.equal(
|
||||
recallContext.recallOptions.cachedRecallPayload.source,
|
||||
"planner-handoff",
|
||||
);
|
||||
|
||||
await harness.result.onGenerationAfterCommands("normal", {}, false);
|
||||
|
||||
assert.equal(harness.runRecallCalls.length, 1);
|
||||
assert.equal(harness.runRecallCalls[0].overrideUserMessage, "planner 原始输入");
|
||||
assert.equal(harness.runRecallCalls[0].overrideSource, "planner-handoff");
|
||||
assert.equal(harness.runRecallCalls[0].authoritativeInputUsed, true);
|
||||
assert.equal(
|
||||
harness.runRecallCalls[0].boundUserFloorText,
|
||||
"楼层里的稳定用户输入",
|
||||
);
|
||||
assert.equal(harness.runRecallCalls[0].includeSyntheticUserMessage, true);
|
||||
assert.ok(harness.runRecallCalls[0].cachedRecallPayload);
|
||||
}
|
||||
|
||||
async function testAuthoritativeSendIntentStaysFrozenAcrossHooksWhenFlagEnabled() {
|
||||
const harness = await createGenerationRecallHarness();
|
||||
harness.extension_settings[MODULE_NAME] = {
|
||||
recallUseAuthoritativeGenerationInput: true,
|
||||
};
|
||||
harness.chat = [{ is_user: true, mes: "稳定 chat tail" }];
|
||||
harness.pendingRecallSendIntent = {
|
||||
text: "第一次权威输入",
|
||||
hash: "hash-phase4-frozen-a",
|
||||
at: Date.now(),
|
||||
source: "dom-intent",
|
||||
};
|
||||
|
||||
await harness.result.onGenerationAfterCommands("normal", {}, false);
|
||||
|
||||
harness.pendingRecallSendIntent = {
|
||||
text: "第二次漂移输入",
|
||||
hash: "hash-phase4-frozen-b",
|
||||
at: Date.now(),
|
||||
source: "dom-intent",
|
||||
};
|
||||
await harness.result.onBeforeCombinePrompts();
|
||||
|
||||
assert.equal(harness.runRecallCalls.length, 1);
|
||||
assert.equal(harness.runRecallCalls[0].overrideUserMessage, "第一次权威输入");
|
||||
assert.equal(harness.runRecallCalls[0].overrideSource, "send-intent");
|
||||
assert.equal(harness.runRecallCalls[0].authoritativeInputUsed, true);
|
||||
assert.equal(harness.runRecallCalls[0].boundUserFloorText, "稳定 chat tail");
|
||||
|
||||
const transaction = [...harness.result.generationRecallTransactions.values()][0];
|
||||
assert.ok(transaction);
|
||||
assert.equal(
|
||||
transaction.frozenRecallOptions.overrideUserMessage,
|
||||
"第一次权威输入",
|
||||
);
|
||||
assert.equal(transaction.frozenRecallOptions.authoritativeInputUsed, true);
|
||||
assert.equal(transaction.frozenRecallOptions.boundUserFloorText, "稳定 chat tail");
|
||||
assert.equal(transaction.frozenRecallOptions.includeSyntheticUserMessage, true);
|
||||
}
|
||||
|
||||
async function testHostSnapshotCanRemainAuthoritativeQueryWhenFlagEnabled() {
|
||||
const harness = await createGenerationRecallHarness();
|
||||
harness.extension_settings[MODULE_NAME] = {
|
||||
@@ -123,6 +237,8 @@ function testResolveRecallInputControllerAppendsSyntheticAuthoritativeUserMessag
|
||||
}
|
||||
|
||||
await testSendIntentCanRemainAuthoritativeQueryWhenFlagEnabled();
|
||||
await testPlannerHandoffCanRemainAuthoritativeQueryWhenFlagEnabled();
|
||||
await testAuthoritativeSendIntentStaysFrozenAcrossHooksWhenFlagEnabled();
|
||||
await testHostSnapshotCanRemainAuthoritativeQueryWhenFlagEnabled();
|
||||
testResolveRecallInputControllerAppendsSyntheticAuthoritativeUserMessage();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const extractProfile = getActiveTaskProfile(
|
||||
assert.equal(extractProfile.taskType, "extract");
|
||||
assert.equal(extractProfile.id, "default");
|
||||
assert.ok(Array.isArray(extractProfile.blocks));
|
||||
assert.equal(extractProfile.blocks.length, 12);
|
||||
assert.equal(extractProfile.blocks.length, 14);
|
||||
assert.deepEqual(
|
||||
extractProfile.blocks.map((block) => block.name),
|
||||
[
|
||||
@@ -48,6 +48,8 @@ assert.deepEqual(
|
||||
"图统计",
|
||||
"Schema",
|
||||
"当前范围",
|
||||
"活跃总结",
|
||||
"故事时间",
|
||||
"输出格式",
|
||||
"行为规则",
|
||||
],
|
||||
@@ -65,6 +67,8 @@ assert.deepEqual(
|
||||
"builtin",
|
||||
"builtin",
|
||||
"builtin",
|
||||
"builtin",
|
||||
"builtin",
|
||||
"custom",
|
||||
"custom",
|
||||
],
|
||||
@@ -82,6 +86,8 @@ assert.deepEqual(
|
||||
"system",
|
||||
"system",
|
||||
"system",
|
||||
"system",
|
||||
"system",
|
||||
"user",
|
||||
"user",
|
||||
],
|
||||
@@ -214,16 +220,16 @@ const upgradedLegacyDefault = getActiveTaskProfile(
|
||||
},
|
||||
"extract",
|
||||
);
|
||||
assert.equal(upgradedLegacyDefault.blocks.length, 12);
|
||||
assert.equal(upgradedLegacyDefault.blocks.length, 14);
|
||||
assert.equal(upgradedLegacyDefault.blocks[0].name, "抬头");
|
||||
assert.match(upgradedLegacyDefault.blocks[0].content, /虚拟的世界/);
|
||||
assert.equal(upgradedLegacyDefault.blocks[0].role, "system");
|
||||
assert.equal(upgradedLegacyDefault.blocks[0].injectionMode, "relative");
|
||||
assert.equal(upgradedLegacyDefault.blocks[1].content, "保留我自己的角色定义");
|
||||
assert.equal(upgradedLegacyDefault.blocks[10].content, "保留我自己的输出格式");
|
||||
assert.equal(upgradedLegacyDefault.blocks[11].content, "保留我自己的行为规则");
|
||||
assert.equal(upgradedLegacyDefault.blocks[10].role, "user");
|
||||
assert.equal(upgradedLegacyDefault.blocks[11].role, "user");
|
||||
assert.equal(upgradedLegacyDefault.blocks[12].content, "保留我自己的输出格式");
|
||||
assert.equal(upgradedLegacyDefault.blocks[13].content, "保留我自己的行为规则");
|
||||
assert.equal(upgradedLegacyDefault.blocks[12].role, "user");
|
||||
assert.equal(upgradedLegacyDefault.blocks[13].role, "user");
|
||||
|
||||
const currentDefaults = createDefaultTaskProfiles();
|
||||
const currentDefaultExtract = currentDefaults.extract.profiles[0];
|
||||
@@ -389,7 +395,7 @@ assert.deepEqual(
|
||||
);
|
||||
assert.ok(
|
||||
upgradedLegacyDefault.blocks
|
||||
.slice(0, 10)
|
||||
.slice(0, 12)
|
||||
.every((block) => block.role === "system"),
|
||||
);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ const activeProfile = getActiveTaskProfile(
|
||||
"extract",
|
||||
);
|
||||
assert.equal(activeProfile.name, "激进提取");
|
||||
assert.equal(activeProfile.blocks.length, 14);
|
||||
assert.equal(activeProfile.blocks.length, 16);
|
||||
const builtinBlock = activeProfile.blocks.find(
|
||||
(block) => block.type === "builtin" && block.sourceKey === "userMessage",
|
||||
);
|
||||
|
||||
@@ -930,7 +930,7 @@ try {
|
||||
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]: 第二句",
|
||||
"#1 [assistant|深度注入 D4]: 这是 d4 atDepth 消息。\n\n#2 [assistant|深度注入]: 这是一条 atDepth 消息。\n\n#11 [user]: 第一句\n\n#4 [assistant|深度注入 D1]: 这是 d1 atDepth 消息。\n\n#12 [assistant]: 第二句",
|
||||
"用户问题:继续调查 depth 排序",
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1513,6 +1513,63 @@
|
||||
<div class="bme-config-help">
|
||||
开启后,最新 AI 楼先不自动提取,要等下一条 AI 楼出现后,才提取前一批内容。提取未处理和范围重提不受影响。
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-extract-recent-message-cap"
|
||||
>最近消息上限(0 = 不限)</label
|
||||
>
|
||||
<input
|
||||
id="bme-setting-extract-recent-message-cap"
|
||||
class="bme-config-input"
|
||||
type="number"
|
||||
min="0"
|
||||
max="200"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-extract-prompt-structured-mode"
|
||||
>提取结构模式</label
|
||||
>
|
||||
<select
|
||||
id="bme-setting-extract-prompt-structured-mode"
|
||||
class="bme-config-input"
|
||||
>
|
||||
<option value="both">混合(transcript + structured)</option>
|
||||
<option value="transcript">仅 transcript</option>
|
||||
<option value="structured">仅 structured</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-extract-worldbook-mode"
|
||||
>提取时世界书</label
|
||||
>
|
||||
<select
|
||||
id="bme-setting-extract-worldbook-mode"
|
||||
class="bme-config-input"
|
||||
>
|
||||
<option value="active">启用(解析激活条目)</option>
|
||||
<option value="none">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
<label
|
||||
class="bme-inline-checkbox"
|
||||
for="bme-setting-extract-include-summaries"
|
||||
>
|
||||
<input
|
||||
id="bme-setting-extract-include-summaries"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>提取时包含活跃总结</span>
|
||||
</label>
|
||||
<label
|
||||
class="bme-inline-checkbox"
|
||||
for="bme-setting-extract-include-story-time"
|
||||
>
|
||||
<input
|
||||
id="bme-setting-extract-include-story-time"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>提取时包含故事时间线</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1545,6 +1602,19 @@
|
||||
max="9999"
|
||||
/>
|
||||
</div>
|
||||
<label
|
||||
class="bme-inline-checkbox"
|
||||
for="bme-setting-recall-use-authoritative-generation-input"
|
||||
>
|
||||
<input
|
||||
id="bme-setting-recall-use-authoritative-generation-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>使用权威 generation 输入(实验性)</span>
|
||||
</label>
|
||||
<div class="bme-config-help">
|
||||
开启后,召回查询将优先使用更接近真实发送入口的文本(如 send-intent、宿主快照、planner handoff),而非回退到 chat tail 或 textarea。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
58
ui/panel.js
58
ui/panel.js
@@ -4377,6 +4377,26 @@ function _refreshConfigTab() {
|
||||
"bme-setting-extract-auto-delay-latest-assistant",
|
||||
settings.extractAutoDelayLatestAssistant === true,
|
||||
);
|
||||
_setInputValue(
|
||||
"bme-setting-extract-recent-message-cap",
|
||||
settings.extractRecentMessageCap ?? 0,
|
||||
);
|
||||
_setInputValue(
|
||||
"bme-setting-extract-prompt-structured-mode",
|
||||
settings.extractPromptStructuredMode || "both",
|
||||
);
|
||||
_setInputValue(
|
||||
"bme-setting-extract-worldbook-mode",
|
||||
settings.extractWorldbookMode || "active",
|
||||
);
|
||||
_setCheckboxValue(
|
||||
"bme-setting-extract-include-summaries",
|
||||
settings.extractIncludeSummaries !== false,
|
||||
);
|
||||
_setCheckboxValue(
|
||||
"bme-setting-extract-include-story-time",
|
||||
settings.extractIncludeStoryTime !== false,
|
||||
);
|
||||
_setInputValue("bme-setting-recall-top-k", settings.recallTopK ?? 20);
|
||||
_setInputValue("bme-setting-recall-max-nodes", settings.recallMaxNodes ?? 8);
|
||||
_setInputValue(
|
||||
@@ -4472,6 +4492,10 @@ function _refreshConfigTab() {
|
||||
settings.recallObjectiveGlobalWeight ?? 0.75,
|
||||
);
|
||||
_setInputValue("bme-setting-inject-depth", settings.injectDepth ?? 9999);
|
||||
_setCheckboxValue(
|
||||
"bme-setting-recall-use-authoritative-generation-input",
|
||||
settings.recallUseAuthoritativeGenerationInput === true,
|
||||
);
|
||||
_setInputValue("bme-setting-graph-weight", settings.graphWeight ?? 0.6);
|
||||
_setInputValue("bme-setting-vector-weight", settings.vectorWeight ?? 0.3);
|
||||
_setInputValue(
|
||||
@@ -4805,6 +4829,35 @@ function _bindConfigControls() {
|
||||
(checked) =>
|
||||
_patchSettings({ extractAutoDelayLatestAssistant: checked }),
|
||||
);
|
||||
bindNumber("bme-setting-extract-recent-message-cap", 0, 0, 200, (value) =>
|
||||
_patchSettings({ extractRecentMessageCap: value }),
|
||||
);
|
||||
const extractStructuredModeEl = document.getElementById(
|
||||
"bme-setting-extract-prompt-structured-mode",
|
||||
);
|
||||
if (extractStructuredModeEl && extractStructuredModeEl.dataset.bmeBound !== "true") {
|
||||
extractStructuredModeEl.addEventListener("change", () => {
|
||||
_patchSettings({ extractPromptStructuredMode: extractStructuredModeEl.value || "both" });
|
||||
});
|
||||
extractStructuredModeEl.dataset.bmeBound = "true";
|
||||
}
|
||||
const extractWorldbookModeEl = document.getElementById(
|
||||
"bme-setting-extract-worldbook-mode",
|
||||
);
|
||||
if (extractWorldbookModeEl && extractWorldbookModeEl.dataset.bmeBound !== "true") {
|
||||
extractWorldbookModeEl.addEventListener("change", () => {
|
||||
_patchSettings({ extractWorldbookMode: extractWorldbookModeEl.value || "active" });
|
||||
});
|
||||
extractWorldbookModeEl.dataset.bmeBound = "true";
|
||||
}
|
||||
bindCheckbox(
|
||||
"bme-setting-extract-include-summaries",
|
||||
(checked) => _patchSettings({ extractIncludeSummaries: checked }),
|
||||
);
|
||||
bindCheckbox(
|
||||
"bme-setting-extract-include-story-time",
|
||||
(checked) => _patchSettings({ extractIncludeStoryTime: checked }),
|
||||
);
|
||||
bindNumber("bme-setting-recall-top-k", 20, 1, 100, (value) =>
|
||||
_patchSettings({ recallTopK: value }),
|
||||
);
|
||||
@@ -4927,6 +4980,11 @@ function _bindConfigControls() {
|
||||
bindNumber("bme-setting-inject-depth", 9999, 0, 9999, (value) =>
|
||||
_patchSettings({ injectDepth: value }),
|
||||
);
|
||||
bindCheckbox(
|
||||
"bme-setting-recall-use-authoritative-generation-input",
|
||||
(checked) =>
|
||||
_patchSettings({ recallUseAuthoritativeGenerationInput: checked }),
|
||||
);
|
||||
bindFloat("bme-setting-graph-weight", 0.6, 0, 1, (value) =>
|
||||
_patchSettings({ graphWeight: value }),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user