phase2-4 recall prompt-flow hardening

This commit is contained in:
Youzini-afk
2026-04-11 18:51:50 +08:00
parent 3a10dbb9ba
commit 0cb95c4f2b
14 changed files with 1069 additions and 143 deletions

View File

@@ -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),
};
}

View File

@@ -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) {