mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
phase2-4 recall prompt-flow hardening
This commit is contained in:
@@ -1865,6 +1865,86 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function clonePayloadMessage(message = {}) {
|
||||
return createExecutionMessage(message.role, message.content, {
|
||||
source: String(message.source || ""),
|
||||
blockId: String(message.blockId || ""),
|
||||
blockName: String(message.blockName || ""),
|
||||
blockType: String(message.blockType || ""),
|
||||
sourceKey: String(message.sourceKey || ""),
|
||||
injectionMode: String(message.injectionMode || ""),
|
||||
derivedFromWorldInfo: message.derivedFromWorldInfo === true,
|
||||
contentOrigin: String(message.contentOrigin || ""),
|
||||
sanitizationEligible: message.sanitizationEligible === true,
|
||||
regexSourceType: String(message.regexSourceType || ""),
|
||||
});
|
||||
}
|
||||
|
||||
function collectPayloadUserMessageTexts(messages = []) {
|
||||
return (Array.isArray(messages) ? messages : [])
|
||||
.filter((message) => String(message?.role || "").trim().toLowerCase() === "user")
|
||||
.map((message) => String(message?.content || "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function buildSafeFallbackUserPrompt(
|
||||
settings = {},
|
||||
taskType,
|
||||
{
|
||||
fallbackUserPrompt = "",
|
||||
blockedContents = [],
|
||||
rawExecutionMessages = [],
|
||||
rawPrivateTaskMessages = [],
|
||||
} = {},
|
||||
) {
|
||||
const structuredUserPrompt = [
|
||||
...collectPayloadUserMessageTexts(rawExecutionMessages),
|
||||
...collectPayloadUserMessageTexts(rawPrivateTaskMessages),
|
||||
]
|
||||
.join("\n\n")
|
||||
.trim();
|
||||
const candidates = [
|
||||
{
|
||||
source: "structured-user-blocks",
|
||||
text: structuredUserPrompt,
|
||||
},
|
||||
{
|
||||
source: "fallback-user-prompt",
|
||||
text: String(fallbackUserPrompt || "").trim(),
|
||||
},
|
||||
].filter((candidate) => candidate.text);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const sanitized = sanitizeInjectionText(settings, taskType, candidate.text, {
|
||||
mode: "final-injection-safe",
|
||||
blockedContents,
|
||||
contentOrigin: PROMPT_CONTENT_ORIGIN.HOST_INJECTED,
|
||||
sanitizationEligible: true,
|
||||
role: "user",
|
||||
applySanitizer: true,
|
||||
applyHostRegex: false,
|
||||
path: "payload.fallbackUserPrompt",
|
||||
stage: "payload-fallback-user-prompt",
|
||||
});
|
||||
const text = String(sanitized.text || "").trim();
|
||||
if (text) {
|
||||
return {
|
||||
text,
|
||||
source: candidate.source,
|
||||
changed: Boolean(sanitized.changed),
|
||||
dropped: Boolean(sanitized.dropped),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: "",
|
||||
source: candidates[0]?.source || "",
|
||||
changed: false,
|
||||
dropped: candidates.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTaskLlmPayload(promptBuild = null, fallbackUserPrompt = "") {
|
||||
const runtimeMvu = promptBuild?.__mvuRuntime || {};
|
||||
const taskType = String(promptBuild?.debug?.taskType || "");
|
||||
@@ -1880,20 +1960,12 @@ export function buildTaskLlmPayload(promptBuild = null, fallbackUserPrompt = "")
|
||||
: [];
|
||||
const rawExecutionMessages = Array.isArray(promptBuild?.executionMessages)
|
||||
? promptBuild.executionMessages
|
||||
.map((message) =>
|
||||
createExecutionMessage(message.role, message.content, {
|
||||
source: String(message.source || ""),
|
||||
blockId: String(message.blockId || ""),
|
||||
blockName: String(message.blockName || ""),
|
||||
blockType: String(message.blockType || ""),
|
||||
sourceKey: String(message.sourceKey || ""),
|
||||
injectionMode: String(message.injectionMode || ""),
|
||||
derivedFromWorldInfo: message.derivedFromWorldInfo === true,
|
||||
contentOrigin: String(message.contentOrigin || ""),
|
||||
sanitizationEligible: message.sanitizationEligible === true,
|
||||
regexSourceType: String(message.regexSourceType || ""),
|
||||
}),
|
||||
)
|
||||
.map((message) => clonePayloadMessage(message))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const rawPrivateTaskMessages = Array.isArray(promptBuild?.privateTaskMessages)
|
||||
? promptBuild.privateTaskMessages
|
||||
.map((message) => clonePayloadMessage(message))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const executionMessages = sanitizePromptMessages(
|
||||
@@ -1949,22 +2021,39 @@ export function buildTaskLlmPayload(promptBuild = null, fallbackUserPrompt = "")
|
||||
: sanitizePromptMessages(
|
||||
settings,
|
||||
taskType,
|
||||
Array.isArray(promptBuild?.privateTaskMessages)
|
||||
? promptBuild.privateTaskMessages
|
||||
: [],
|
||||
rawPrivateTaskMessages,
|
||||
{
|
||||
blockedContents,
|
||||
applySanitizer: (message) =>
|
||||
!(isCustomFilter && messageUsesWorldInfoContent(message)),
|
||||
},
|
||||
);
|
||||
const hasAdditionalUserMessage = additionalMessages.some(
|
||||
(message) => message.role === "user",
|
||||
);
|
||||
const fallbackUserPromptResult =
|
||||
hasUserMessage || hasAdditionalUserMessage
|
||||
? {
|
||||
text: "",
|
||||
source: hasUserMessage ? "execution-messages" : "additional-messages",
|
||||
changed: false,
|
||||
dropped: false,
|
||||
}
|
||||
: buildSafeFallbackUserPrompt(settings, taskType, {
|
||||
fallbackUserPrompt,
|
||||
blockedContents,
|
||||
rawExecutionMessages,
|
||||
rawPrivateTaskMessages,
|
||||
});
|
||||
|
||||
return {
|
||||
systemPrompt:
|
||||
executionMessages.length > 0 ? "" : String(promptBuild?.systemPrompt || ""),
|
||||
userPrompt: hasUserMessage ? "" : String(fallbackUserPrompt || ""),
|
||||
userPrompt: fallbackUserPromptResult.text,
|
||||
promptMessages: executionMessages,
|
||||
additionalMessages,
|
||||
fallbackUserPromptSource: fallbackUserPromptResult.source,
|
||||
fallbackUserPromptApplied: Boolean(fallbackUserPromptResult.text),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -256,6 +256,10 @@ function getRegexHost() {
|
||||
const capabilitySupport = regexHost.readCapabilitySupport?.() || {};
|
||||
const supplementedCapabilities = [];
|
||||
const missingCapabilities = [];
|
||||
const resolvedGetter =
|
||||
typeof regexHost.getTavernRegexes === "function"
|
||||
? regexHost.getTavernRegexes
|
||||
: legacyGetTavernRegexes;
|
||||
const resolvedCharacterToggle =
|
||||
typeof regexHost.isCharacterTavernRegexesEnabled === "function"
|
||||
? regexHost.isCharacterTavernRegexesEnabled
|
||||
@@ -265,6 +269,14 @@ function getRegexHost() {
|
||||
? regexHost.formatAsTavernRegexedString
|
||||
: legacyFormatAsTavernRegexedString;
|
||||
|
||||
if (typeof regexHost.getTavernRegexes !== "function") {
|
||||
if (resolvedGetter) {
|
||||
supplementedCapabilities.push("getTavernRegexes");
|
||||
} else {
|
||||
missingCapabilities.push("getTavernRegexes");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof regexHost.isCharacterTavernRegexesEnabled !== "function") {
|
||||
if (resolvedCharacterToggle) {
|
||||
supplementedCapabilities.push("isCharacterTavernRegexesEnabled");
|
||||
@@ -282,16 +294,24 @@ function getRegexHost() {
|
||||
}
|
||||
|
||||
return {
|
||||
getTavernRegexes: regexHost.getTavernRegexes,
|
||||
getTavernRegexes: resolvedGetter,
|
||||
isCharacterTavernRegexesEnabled: resolvedCharacterToggle,
|
||||
formatAsTavernRegexedString: resolvedFormatter,
|
||||
sourceLabel: capabilitySupport.sourceLabel || "host-adapter.regex",
|
||||
sourceLabel:
|
||||
capabilitySupport.sourceLabel || regexHost?.sourceLabel || "host-adapter.regex",
|
||||
fallback:
|
||||
Boolean(capabilitySupport.fallback) ||
|
||||
typeof regexHost.getTavernRegexes !== "function" ||
|
||||
typeof regexHost.isCharacterTavernRegexesEnabled !== "function" ||
|
||||
typeof regexHost.formatAsTavernRegexedString !== "function" ||
|
||||
supplementedCapabilities.length > 0,
|
||||
fallbackReason: String(capabilitySupport.fallbackReason || "").trim(),
|
||||
fallbackReason: String(
|
||||
regexHost?.fallbackReason || capabilitySupport.fallbackReason || "",
|
||||
).trim(),
|
||||
capabilityStatus: Object.freeze({
|
||||
mode: capabilitySupport.mode || "unknown",
|
||||
mode: capabilitySupport.mode || regexHost?.mode || "unknown",
|
||||
bridgeTier:
|
||||
capabilitySupport.bridgeTier || capabilitySupport.mode || regexHost?.mode || "unknown",
|
||||
supplementedCapabilities: Object.freeze(supplementedCapabilities),
|
||||
missingCapabilities: Object.freeze(missingCapabilities),
|
||||
}),
|
||||
@@ -500,10 +520,15 @@ function summarizeRuleForPromptPreview(rule, stageConfig = {}, reason = "") {
|
||||
promptStageMode = promptSemanticApplies ? "replace" : "skip";
|
||||
} else if (rule?.destinationFlags?.prompt === false || summary.markdownOnly) {
|
||||
promptStageMode = "display-only";
|
||||
} else if (summary.beautificationReplace && executionState.mode !== "host-real") {
|
||||
} else if (
|
||||
summary.beautificationReplace &&
|
||||
!["host-real", "host-helper"].includes(executionState.mode)
|
||||
) {
|
||||
promptStageMode = "fallback-skip-beautify";
|
||||
} else if (executionState.mode === "host-real") {
|
||||
promptStageMode = "host-real";
|
||||
} else if (executionState.mode === "host-helper") {
|
||||
promptStageMode = "host-helper";
|
||||
} else if (executionState.mode === "host-fallback") {
|
||||
promptStageMode = "host-fallback";
|
||||
}
|
||||
@@ -748,6 +773,10 @@ function collectTavernRulesDetailed(regexConfig = {}) {
|
||||
formatterAvailable:
|
||||
typeof regexHost.formatAsTavernRegexedString === "function",
|
||||
executionMode: buildHostRegexExecutionState(regexHost).mode,
|
||||
bridgeTier:
|
||||
regexHost?.capabilityStatus?.bridgeTier ||
|
||||
regexHost?.capabilityStatus?.mode ||
|
||||
"unknown",
|
||||
capabilityStatus: regexHost.capabilityStatus || null,
|
||||
},
|
||||
sources,
|
||||
@@ -822,21 +851,40 @@ function ruleMatchesFormatterDepth(rule, formatterOptions = null) {
|
||||
}
|
||||
|
||||
function buildHostRegexExecutionState(regexHost = null) {
|
||||
const bridgeTier =
|
||||
String(
|
||||
regexHost?.capabilityStatus?.bridgeTier ||
|
||||
regexHost?.capabilityStatus?.mode ||
|
||||
"",
|
||||
).trim() || "unknown";
|
||||
const formatterAvailable =
|
||||
typeof regexHost?.formatAsTavernRegexedString === "function";
|
||||
const rulesAvailable = typeof regexHost?.getTavernRegexes === "function";
|
||||
|
||||
if (formatterAvailable) {
|
||||
if (formatterAvailable && bridgeTier === "core-real") {
|
||||
return {
|
||||
mode: "host-real",
|
||||
bridgeTier,
|
||||
formatterAvailable: true,
|
||||
fallbackReason: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (formatterAvailable) {
|
||||
return {
|
||||
mode: "host-helper",
|
||||
bridgeTier,
|
||||
formatterAvailable: true,
|
||||
fallbackReason:
|
||||
String(regexHost?.fallbackReason || "").trim() ||
|
||||
"当前通过 helper bridge 提供 Tavern Regex formatter",
|
||||
};
|
||||
}
|
||||
|
||||
if (rulesAvailable) {
|
||||
return {
|
||||
mode: "host-fallback",
|
||||
bridgeTier,
|
||||
formatterAvailable: false,
|
||||
fallbackReason:
|
||||
String(regexHost?.fallbackReason || "").trim() ||
|
||||
@@ -846,6 +894,7 @@ function buildHostRegexExecutionState(regexHost = null) {
|
||||
|
||||
return {
|
||||
mode: "host-unavailable",
|
||||
bridgeTier,
|
||||
formatterAvailable: false,
|
||||
fallbackReason:
|
||||
String(regexHost?.fallbackReason || "").trim() ||
|
||||
@@ -864,7 +913,7 @@ function shouldReuseTavernRuleForPrompt(rule, executionMode = "host-fallback") {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
executionMode !== "host-real" &&
|
||||
!["host-real", "host-helper"].includes(executionMode) &&
|
||||
Boolean(rule?.beautificationReplace)
|
||||
) {
|
||||
return false;
|
||||
@@ -1106,7 +1155,10 @@ export function applyHostRegexReuse(
|
||||
|
||||
if (
|
||||
!normalizedSourceType ||
|
||||
(tavernRules.length === 0 && executionState.mode !== "host-real")
|
||||
(
|
||||
tavernRules.length === 0 &&
|
||||
!["host-real", "host-helper"].includes(executionState.mode)
|
||||
)
|
||||
) {
|
||||
pushDebug(debugCollector, {
|
||||
kind: "host-reuse",
|
||||
@@ -1133,7 +1185,7 @@ export function applyHostRegexReuse(
|
||||
}
|
||||
|
||||
if (
|
||||
executionState.mode === "host-real" &&
|
||||
["host-real", "host-helper"].includes(executionState.mode) &&
|
||||
typeof regexHost?.formatAsTavernRegexedString === "function"
|
||||
) {
|
||||
try {
|
||||
@@ -1150,23 +1202,29 @@ export function applyHostRegexReuse(
|
||||
taskType: normalizedTaskType,
|
||||
stage: `host:${normalizedSourceType}`,
|
||||
enabled: true,
|
||||
executionMode: "host-real",
|
||||
executionMode: executionState.mode,
|
||||
formatterAvailable: true,
|
||||
appliedRules: output !== input
|
||||
? [{ id: "__host_formatter__", source: "host-real" }]
|
||||
? [{ id: "__host_formatter__", source: executionState.mode }]
|
||||
: [],
|
||||
sourceCount: { tavern: tavernRules.length, local: 0 },
|
||||
fallbackReason: "",
|
||||
fallbackReason:
|
||||
executionState.mode === "host-real"
|
||||
? ""
|
||||
: executionState.fallbackReason,
|
||||
hostFormatterSource: String(regexHost?.sourceLabel || ""),
|
||||
skippedDisplayOnlyRuleCount,
|
||||
});
|
||||
return {
|
||||
text: output,
|
||||
changed: output !== input,
|
||||
executionMode: "host-real",
|
||||
executionMode: executionState.mode,
|
||||
formatterAvailable: true,
|
||||
formatterSource: String(regexHost?.sourceLabel || ""),
|
||||
fallbackReason: "",
|
||||
fallbackReason:
|
||||
executionState.mode === "host-real"
|
||||
? ""
|
||||
: executionState.fallbackReason,
|
||||
skippedDisplayOnlyRuleCount,
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user