From bd4e82c12cb9dddf0d92f7f02447734228ed2ed6 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:13:36 +0800 Subject: [PATCH 1/5] Add diagnostic logging for user-block fallback in prompt builder When user-role blocks are missing from executionMessages, logs details about where content was lost (sanitization, blockedContents, etc.) to help diagnose why custom extract prompt falls back to hardcoded default. Co-Authored-By: Claude Opus 4.6 --- prompt-builder.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/prompt-builder.js b/prompt-builder.js index ed727b4..8c9b924 100644 --- a/prompt-builder.js +++ b/prompt-builder.js @@ -1137,7 +1137,19 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { } content = sanitizedBlockContent.text; - if (!String(content || "").trim()) continue; + if (!String(content || "").trim()) { + if (role === "user" && String(block.content || "").trim()) { + console.warn( + `[ST-BME] buildTaskPrompt: user block "${block.name || block.id}" ` + + `content emptied during sanitization! ` + + `original length=${String(block.content || "").length}, ` + + `dropped=${sanitizedBlockContent.dropped}, ` + + `reasons=[${(sanitizedBlockContent.reasons || []).join(", ")}], ` + + `blockedHitCount=${sanitizedBlockContent.blockedHitCount}`, + ); + } + continue; + } const mode = normalizeInjectionMode(block.injectionMode); renderedBlocks.push({ @@ -1361,6 +1373,39 @@ export function buildTaskLlmPayload(promptBuild = null, fallbackUserPrompt = "") const hasUserMessage = executionMessages.some( (message) => message.role === "user", ); + if (!hasUserMessage && rawExecutionMessages.length > 0) { + const userBlocksBefore = (promptBuild?.executionMessages || []).filter( + (m) => m?.role === "user", + ); + const userBlocksAfterRaw = rawExecutionMessages.filter( + (m) => m?.role === "user", + ); + const userBlocksAfterSanitize = executionMessages.filter( + (m) => m?.role === "user", + ); + console.warn( + `[ST-BME] buildTaskLlmPayload fallback triggered: ` + + `user blocks in promptBuild=${userBlocksBefore.length}, ` + + `after recreate=${userBlocksAfterRaw.length}, ` + + `after sanitize=${userBlocksAfterSanitize.length}, ` + + `blockedContents count=${blockedContents.length}, ` + + `total executionMessages=${executionMessages.length}`, + ); + if (userBlocksBefore.length > 0) { + for (const block of userBlocksBefore) { + console.warn( + `[ST-BME] user block "${block.blockName || block.blockId}": ` + + `content length=${String(block.content || "").length}, ` + + `content preview="${String(block.content || "").slice(0, 80)}..."`, + ); + } + } + if (blockedContents.length > 0) { + console.warn( + `[ST-BME] blockedContents lengths: [${blockedContents.map((c) => String(c || "").length).join(", ")}]`, + ); + } + } const sanitizedFallbackUserPrompt = sanitizeTaskPromptText( {}, promptBuild?.debug?.taskType || "", From 5404cd7fadcae41776827adb0fd355a003b3541a Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:21:59 +0800 Subject: [PATCH 2/5] Add detailed block-level diagnostic logging for prompt build Logs all block names/roles/enabled status at loop entry, user-block content lengths before sanitization, and final execution message counts to pinpoint exactly where user-role blocks disappear. Co-Authored-By: Claude Opus 4.6 --- prompt-builder.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/prompt-builder.js b/prompt-builder.js index 8c9b924..c19f257 100644 --- a/prompt-builder.js +++ b/prompt-builder.js @@ -1098,6 +1098,12 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { let assistantRoleBlockCount = 0; let systemRoleBlockCount = 0; + console.log( + `[ST-BME][prompt-diag] buildTaskPrompt: taskType=${taskType}, ` + + `total blocks=${blocks.length}, ` + + `block roles=[${blocks.map((b) => `${b.name}(${b.role},${b.enabled !== false ? "on" : "off"})`).join(", ")}]`, + ); + for (const block of blocks) { if (!block || block.enabled === false) continue; @@ -1118,6 +1124,15 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { content = interpolateVariables(block.content || "", resolvedContext); } + if (role === "user") { + console.log( + `[ST-BME][prompt-diag] user block "${block.name || block.id}": ` + + `type=${block.type}, contentLen=${String(content || "").length}, ` + + `rawContentLen=${String(block.content || "").length}, ` + + `blockedContentsCount=${worldInfoRuntimeBlockedContents.length}`, + ); + } + const sanitizedBlockContent = sanitizeTaskPromptText( settings, taskType, @@ -1205,6 +1220,13 @@ export async function buildTaskPrompt(settings = {}, taskType, context = {}) { } } + console.log( + `[ST-BME][prompt-diag] buildTaskPrompt done: ` + + `executionMessages=${executionMessages.length}, ` + + `userBlocks=${userRoleBlockCount}, systemBlocks=${systemRoleBlockCount}, ` + + `customMessages=${customMessages.length}`, + ); + for (const message of worldInfoResolution.additionalMessages || []) { const executionMessage = createExecutionMessage( message.role, From 1cc613f68f36e31eddc38d3f5f096be8517c9ee1 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:25:49 +0800 Subject: [PATCH 3/5] Add post-payload diagnostic to track user messages after resolveTaskPromptPayload Logs promptMessages count, user message count, and content preview right before callLLMForJSON to identify exactly where user blocks vanish. Co-Authored-By: Claude Opus 4.6 --- extractor.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/extractor.js b/extractor.js index 5b4f946..930e5c8 100644 --- a/extractor.js +++ b/extractor.js @@ -411,6 +411,33 @@ export async function extractMemories({ systemPrompt, ); + // 诊断:追踪 promptPayload + { + const pm = Array.isArray(promptPayload.promptMessages) ? promptPayload.promptMessages : []; + const pmUser = pm.filter((m) => m?.role === "user"); + const am = Array.isArray(promptPayload.additionalMessages) ? promptPayload.additionalMessages : []; + console.log( + `[ST-BME][prompt-diag] resolveTaskPromptPayload: ` + + `promptMessages=${pm.length} (user=${pmUser.length}), ` + + `additionalMessages=${am.length}, ` + + `userPrompt length=${String(promptPayload.userPrompt || "").length}, ` + + `systemPrompt length=${String(promptPayload.systemPrompt || "").length}, ` + + `llmSystemPrompt length=${String(llmSystemPrompt || "").length}`, + ); + if (pmUser.length > 0) { + for (const m of pmUser) { + console.log( + `[ST-BME][prompt-diag] user msg: contentLen=${String(m.content || "").length}, ` + + `blockName="${m.blockName || ""}", preview="${String(m.content || "").slice(0, 60)}..."`, + ); + } + } else { + console.warn( + `[ST-BME][prompt-diag] NO user messages in promptMessages! Fallback userPrompt will be used.`, + ); + } + } + // 调用 LLM const result = await callLLMForJSON({ systemPrompt: llmSystemPrompt, From 3e834663308f912ebc9165a9f571ea1b6f16c9c7 Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:28:43 +0800 Subject: [PATCH 4/5] Add diagnostics in callLLMForJSON to track message assembly and regex cleaning Logs message counts and roles after buildJsonAttemptMessages and applyTaskFinalInputRegex to identify if user messages are lost during final assembly or regex cleaning stage. Co-Authored-By: Claude Opus 4.6 --- llm.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/llm.js b/llm.js index 0c8f406..d32054d 100644 --- a/llm.js +++ b/llm.js @@ -1583,10 +1583,43 @@ export async function callLLMForJSON({ additionalMessages, promptMessages, ); + { + const asmUser = assembledMessages.filter((m) => m?.role === "user"); + console.log( + `[ST-BME][prompt-diag] buildJsonAttemptMessages: ` + + `total=${assembledMessages.length}, user=${asmUser.length}, ` + + `roles=[${assembledMessages.map((m) => m?.role).join(",")}]`, + ); + for (const m of asmUser) { + console.log( + `[ST-BME][prompt-diag] assembled user: len=${String(m.content || "").length}, ` + + `preview="${String(m.content || "").slice(0, 80)}..."`, + ); + } + } const requestCleaning = applyTaskFinalInputRegex( taskType, assembledMessages, ); + { + const rcMsgs = Array.isArray(requestCleaning.messages) ? requestCleaning.messages : []; + const rcUser = rcMsgs.filter((m) => m?.role === "user"); + const dbg = requestCleaning.debug || {}; + console.log( + `[ST-BME][prompt-diag] applyTaskFinalInputRegex: ` + + `total=${rcMsgs.length}, user=${rcUser.length}, ` + + `changed=${dbg.changed}, applied=${dbg.applied}, ` + + `roles=[${rcMsgs.map((m) => m?.role).join(",")}]`, + ); + if (rcUser.length === 0 && assembledMessages.filter((m) => m?.role === "user").length > 0) { + console.warn( + `[ST-BME][prompt-diag] *** USER MESSAGES LOST during applyTaskFinalInputRegex! ***`, + ); + for (const rule of dbg.appliedRules || []) { + console.warn(`[ST-BME][prompt-diag] applied rule: ${JSON.stringify(rule)}`); + } + } + } const promptExecutionSnapshot = attachRequestCleaningToPromptExecution( promptExecutionSummary, requestCleaning.debug, From 401228825f07aaba22a6c773c967bf3a417f54fd Mon Sep 17 00:00:00 2001 From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:05:48 +0800 Subject: [PATCH 5/5] Fix task regex stage alias precedence --- default-task-profile-templates.js | 18 ------------------ prompt-profiles.js | 31 +++++++++++++------------------ tests/task-regex.mjs | 28 +++++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/default-task-profile-templates.js b/default-task-profile-templates.js index 36329b9..47ae4e2 100644 --- a/default-task-profile-templates.js +++ b/default-task-profile-templates.js @@ -189,13 +189,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -396,13 +393,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -579,13 +573,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -774,13 +765,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -981,13 +969,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, @@ -1200,13 +1185,10 @@ export const DEFAULT_TASK_PROFILE_TEMPLATES = { "character": true }, "stages": { - "finalPrompt": true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - "rawResponse": false, - "beforeParse": false, "output.rawResponse": false, "output.beforeParse": false, "input": true, diff --git a/prompt-profiles.js b/prompt-profiles.js index 695997e..aece4cc 100644 --- a/prompt-profiles.js +++ b/prompt-profiles.js @@ -637,15 +637,13 @@ export function normalizeTaskRegexStages(stages = {}) { for (const [legacyKey, canonicalKey] of Object.entries( TASK_REGEX_STAGE_ALIAS_MAP, )) { - if (Object.prototype.hasOwnProperty.call(source, legacyKey)) { - // Older exports may carry both legacy and canonical keys at the same - // time. When that happens, keep the legacy intent instead of letting a - // newer placeholder default silently flip stage timing. - normalized[canonicalKey] = Boolean(source[legacyKey]); + if (Object.prototype.hasOwnProperty.call(source, canonicalKey)) { + // Respect an explicitly stored canonical key when both forms are + // present. Legacy aliases should only backfill older exports. continue; } - if (Object.prototype.hasOwnProperty.call(source, canonicalKey)) { - normalized[canonicalKey] = Boolean(source[canonicalKey]); + if (Object.prototype.hasOwnProperty.call(source, legacyKey)) { + normalized[canonicalKey] = Boolean(source[legacyKey]); } } @@ -893,13 +891,10 @@ function createFallbackDefaultTaskProfile(taskType) { character: true, }, stages: normalizeTaskRegexStages({ - finalPrompt: true, "input.userMessage": false, "input.recentMessages": false, "input.candidateText": false, "input.finalPrompt": false, - rawResponse: false, - beforeParse: false, "output.rawResponse": false, "output.beforeParse": false, }), @@ -954,10 +949,10 @@ export function createDefaultTaskProfile(taskType) { ...fallback.regex.sources, ...(template?.regex?.sources || {}), }, - stages: normalizeTaskRegexStages({ - ...fallback.regex.stages, - ...(template?.regex?.stages || {}), - }), + stages: { + ...normalizeTaskRegexStages(fallback.regex.stages || {}), + ...normalizeTaskRegexStages(template?.regex?.stages || {}), + }, localRules: Array.isArray(template?.regex?.localRules) ? template.regex.localRules.map((rule, index) => normalizeRegexLocalRule(rule, taskType, index), @@ -1148,10 +1143,10 @@ export function normalizeTaskProfile(taskType, profile = {}, settings = {}) { ...base.regex.sources, ...(profile?.regex?.sources || {}), }, - stages: normalizeTaskRegexStages({ - ...base.regex.stages, - ...(profile?.regex?.stages || {}), - }), + stages: { + ...normalizeTaskRegexStages(base.regex.stages || {}), + ...normalizeTaskRegexStages(profile?.regex?.stages || {}), + }, localRules: Array.isArray(profile?.regex?.localRules) ? profile.regex.localRules.map((rule, index) => normalizeRegexLocalRule(rule, taskType, index), diff --git a/tests/task-regex.mjs b/tests/task-regex.mjs index eba3653..8bad23c 100644 --- a/tests/task-regex.mjs +++ b/tests/task-regex.mjs @@ -156,6 +156,7 @@ try { const { createDefaultTaskProfiles, isTaskRegexStageEnabled, + normalizeTaskProfile, normalizeTaskRegexStages, } = await import("../prompt-profiles.js"); @@ -170,7 +171,7 @@ try { "output.rawResponse": false, "output.beforeParse": false, }); - assert.equal(normalizedLegacyStages["input.finalPrompt"], true); + assert.equal(normalizedLegacyStages["input.finalPrompt"], false); assert.equal(normalizedLegacyStages["input.userMessage"], false); assert.equal(normalizedLegacyStages["input.recentMessages"], false); assert.equal(normalizedLegacyStages["input.candidateText"], false); @@ -178,7 +179,7 @@ try { assert.equal(normalizedLegacyStages["output.beforeParse"], false); assert.equal( isTaskRegexStageEnabled(normalizedLegacyStages, "input.finalPrompt"), - true, + false, ); assert.equal( isTaskRegexStageEnabled(normalizedLegacyStages, "input.userMessage"), @@ -198,7 +199,7 @@ try { defaultProfiles.extract?.profiles?.[0]?.regex?.stages || {}; assert.equal( isTaskRegexStageEnabled(defaultExtractStages, "input.finalPrompt"), - true, + false, ); assert.equal( isTaskRegexStageEnabled(defaultExtractStages, "input.userMessage"), @@ -213,6 +214,27 @@ try { false, ); + const normalizedLegacyOnlyProfile = normalizeTaskProfile( + "extract", + { + id: "legacy-only-profile", + name: "legacy only", + regex: { + stages: { + finalPrompt: true, + }, + }, + }, + {}, + ); + assert.equal( + isTaskRegexStageEnabled( + normalizedLegacyOnlyProfile.regex?.stages || {}, + "input.finalPrompt", + ), + true, + ); + globalThis.getTavernRegexes = () => { throw new Error("legacy global getter should not be used in regex tests"); };