chore: final cleanup

This commit is contained in:
Subarashimo
2025-12-05 18:10:21 +01:00
parent 38328de1bf
commit 7e47dbfd7c
29 changed files with 338 additions and 2168 deletions
+20 -66
View File
@@ -10,17 +10,11 @@ import {
lastGeneratedData,
committedTrackerData,
isGenerating,
lastActionWasSwipe,
setIsGenerating,
setLastActionWasSwipe
setIsGenerating
} from '../../core/state.js';
import { saveChatData } from '../../core/persistence.js';
import { generateSeparateUpdatePrompt } from './promptBuilder.js';
import { parseResponse, parseUserStats, parseSkills, tryParseJSONResponse } from './parser.js';
import { renderUserStats } from '../rendering/userStats.js';
import { renderInfoBox } from '../rendering/infoBox.js';
import { renderThoughts } from '../rendering/thoughts.js';
import { renderInventory } from '../rendering/inventory.js';
import { renderQuests } from '../rendering/quests.js';
import { renderSkills } from '../rendering/skills.js';
import { i18n } from '../../core/i18n.js';
@@ -37,12 +31,8 @@ async function getCurrentPresetName() {
// Use /preset without arguments to get the current preset name
const result = await executeSlashCommandsOnChatInput('/preset', { quiet: true });
// console.log('[RPG Companion] /preset result:', result);
// The result should be an object with a 'pipe' property containing the preset name
if (result && typeof result === 'object' && result.pipe) {
const presetName = String(result.pipe).trim();
// console.log('[RPG Companion] Extracted preset name:', presetName);
return presetName || null;
}
@@ -63,11 +53,7 @@ async function getCurrentPresetName() {
*/
async function switchToPreset(presetName) {
try {
// Use the /preset slash command to switch presets
// This is the proper way to change presets in SillyTavern
await executeSlashCommandsOnChatInput(`/preset ${presetName}`, { quiet: true });
// console.log(`[RPG Companion] Switched to preset "${presetName}"`);
return true;
} catch (error) {
console.error('[RPG Companion] Error switching preset:', error);
@@ -88,7 +74,6 @@ async function switchToPreset(presetName) {
*/
export async function updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory) {
if (isGenerating) {
// console.log('[RPG Companion] Already generating, skipping...');
return;
}
@@ -97,7 +82,6 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
}
if (extensionSettings.generationMode !== 'separate') {
// console.log('[RPG Companion] Not in separate mode, skipping manual update');
return;
}
@@ -133,9 +117,6 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
});
if (response) {
// console.log('[RPG Companion] Raw AI response:', response);
// Try JSON parsing first if structured data mode is enabled
const jsonParsed = tryParseJSONResponse(response);
if (jsonParsed) {
@@ -149,23 +130,13 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
renderSkills();
saveChatData();
} else {
// JSON parsing failed - try legacy text-based parsing as fallback
console.warn('[RPG Companion] JSON parsing failed, attempting legacy text parsing...');
const parsedData = parseResponse(response);
// console.log('[RPG Companion] Parsed data:', parsedData);
// console.log('[RPG Companion] parsedData.userStats:', parsedData.userStats ? parsedData.userStats.substring(0, 100) + '...' : 'null');
// Legacy text parsing does not provide structured characters; clear stale structured data
extensionSettings.charactersData = [];
const parsedCharacterThoughts = parsedData.characterThoughts || '';
extensionSettings.charactersData = [];
const parsedCharacterThoughts = parsedData.characterThoughts || '';
// DON'T update lastGeneratedData here - it should only reflect the data
// from the assistant message the user replied to, not auto-generated updates
// This ensures swipes/regenerations use consistent source data
// Store RPG data for the last assistant message (separate mode)
const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null;
// console.log('[RPG Companion] Last message is_user:', lastMessage ? lastMessage.is_user : 'no message');
if (lastMessage && !lastMessage.is_user) {
if (!lastMessage.extra) {
lastMessage.extra = {};
@@ -175,16 +146,13 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
}
const currentSwipeId = lastMessage.swipe_id || 0;
lastMessage.extra.rpg_companion_swipes[currentSwipeId] = {
userStats: parsedData.userStats,
infoBox: parsedData.infoBox,
characterThoughts: parsedCharacterThoughts
};
lastMessage.extra.rpg_companion_swipes[currentSwipeId] = {
userStats: parsedData.userStats,
infoBox: parsedData.infoBox,
characterThoughts: parsedCharacterThoughts
};
// console.log('[RPG Companion] Stored separate mode RPG data for message swipe', currentSwipeId);
// Update lastGeneratedData for display AND future commit
if (parsedData.userStats) {
if (parsedData.userStats) {
lastGeneratedData.userStats = parsedData.userStats;
parseUserStats(parsedData.userStats);
}
@@ -194,28 +162,19 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
if (parsedData.infoBox) {
lastGeneratedData.infoBox = parsedData.infoBox;
}
lastGeneratedData.characterThoughts = parsedCharacterThoughts;
// console.log('[RPG Companion] 💾 SEPARATE MODE: Updated lastGeneratedData:', {
// userStats: lastGeneratedData.userStats ? 'exists' : 'null',
// infoBox: lastGeneratedData.infoBox ? 'exists' : 'null',
// characterThoughts: lastGeneratedData.characterThoughts ? 'exists' : 'null'
// });
lastGeneratedData.characterThoughts = parsedCharacterThoughts;
// Only auto-commit on TRULY first generation (no committed data exists at all)
// This prevents auto-commit after refresh when we have saved committed data
const hasAnyCommittedContent = (
(committedTrackerData.userStats && committedTrackerData.userStats.trim() !== '') ||
(committedTrackerData.infoBox && committedTrackerData.infoBox.trim() !== '' && committedTrackerData.infoBox !== 'Info Box\n---\n') ||
(committedTrackerData.characterThoughts && committedTrackerData.characterThoughts.trim() !== '' && committedTrackerData.characterThoughts !== 'Present Characters\n---\n')
);
const hasAnyCommittedContent = (
(committedTrackerData.userStats && committedTrackerData.userStats.trim() !== '') ||
(committedTrackerData.infoBox && committedTrackerData.infoBox.trim() !== '' && committedTrackerData.infoBox !== 'Info Box\n---\n') ||
(committedTrackerData.characterThoughts && committedTrackerData.characterThoughts.trim() !== '' && committedTrackerData.characterThoughts !== 'Present Characters\n---\n')
);
// Only commit if we have NO committed content at all (truly first time ever)
if (!hasAnyCommittedContent) {
committedTrackerData.userStats = parsedData.userStats;
committedTrackerData.infoBox = parsedData.infoBox;
committedTrackerData.characterThoughts = parsedCharacterThoughts;
// console.log('[RPG Companion] 🔆 FIRST TIME: Auto-committed tracker data');
}
if (!hasAnyCommittedContent) {
committedTrackerData.userStats = parsedData.userStats;
committedTrackerData.infoBox = parsedData.infoBox;
committedTrackerData.characterThoughts = parsedCharacterThoughts;
}
// Render the updated data
renderUserStats();
@@ -254,14 +213,9 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
setIsGenerating(false);
// Restore button to original state
const $updateBtn = $('#rpg-manual-update');
const refreshText = i18n.getTranslation('template.mainPanel.refreshRpgInfo') || 'Refresh RPG Info';
$updateBtn.html(`<i class="fa-solid fa-sync"></i> ${refreshText}`).prop('disabled', false);
// Reset the flag after tracker generation completes
// This ensures the flag persists through both main generation AND tracker generation
// console.log('[RPG Companion] 🔄 Tracker generation complete - resetting lastActionWasSwipe to false');
setLastActionWasSwipe(false);
}
}
+5 -106
View File
@@ -9,9 +9,7 @@ import {
extensionSettings,
committedTrackerData,
lastGeneratedData,
isGenerating,
lastActionWasSwipe,
setLastActionWasSwipe
isGenerating
} from '../../core/state.js';
import { evaluateSuppression } from './suppression.js';
import { parseUserStats } from './parser.js';
@@ -39,15 +37,7 @@ function getTrackerInstructions(includeHtmlPrompt, includeContinuation) {
* @param {Object} data - Event data
*/
export function onGenerationStarted(type, data) {
// console.log('[RPG Companion] onGenerationStarted called');
// console.log('[RPG Companion] enabled:', extensionSettings.enabled);
// console.log('[RPG Companion] generationMode:', extensionSettings.generationMode);
// console.log('[RPG Companion] ⚡ EVENT: onGenerationStarted - lastActionWasSwipe =', lastActionWasSwipe, '| isGenerating =', isGenerating);
// console.log('[RPG Companion] Committed Prompt:', committedTrackerData);
// Skip tracker injection for image generation requests
if (data?.quietImage) {
// console.log('[RPG Companion] Detected image generation (quietImage=true), skipping tracker injection');
return;
}
@@ -57,74 +47,27 @@ export function onGenerationStarted(type, data) {
const context = getContext();
const chat = context.chat;
// Detect if a guided generation is active (GuidedGenerations and similar extensions
// inject an ephemeral 'instruct' injection into chatMetadata.script_injects).
// If present, we should avoid injecting RPG tracker instructions that ask
// the model to include stats/etc. This prevents conflicts when guided prompts
// are used (e.g., GuidedGenerations Extension).
// Evaluate suppression using the shared helper
const suppression = evaluateSuppression(extensionSettings, context, data);
const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt, instructContent, quietPromptRaw, matchedPattern } = suppression;
const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt } = suppression;
if (shouldSuppress) {
// Debugging: indicate active suppression and which source triggered it
console.debug(`[RPG Companion] Suppression active (mode=${skipMode}). isGuided=${isGuidedGeneration}, isImpersonation=${isImpersonationGeneration}, hasQuietPrompt=${hasQuietPrompt} - skipping RPG tracker injections for this generation.`);
// Also clear any existing RPG Companion prompts so they do not leak into this generation
// (e.g., previously set extension prompts should not be used alongside a guided prompt)
setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
}
const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null;
// For SEPARATE mode only: Check if we need to commit extension data
// BUT: Only do this for the MAIN generation, not the tracker update generation
// If isGenerating is true, this is the tracker update generation (second call), so skip flag logic
// console.log('[RPG Companion DEBUG] Before generating:', lastGeneratedData.characterThoughts, ' , committed - ', committedTrackerData.characterThoughts);
if (extensionSettings.generationMode === 'separate' && !isGenerating) {
if (!lastActionWasSwipe) {
// User sent a new message - commit lastGeneratedData before generation
// console.log('[RPG Companion] 📝 COMMIT: New message - committing lastGeneratedData');
// console.log('[RPG Companion] BEFORE commit - committedTrackerData:', {
// userStats: committedTrackerData.userStats ? 'exists' : 'null',
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
// characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null'
// });
// console.log('[RPG Companion] BEFORE commit - lastGeneratedData:', {
// userStats: lastGeneratedData.userStats ? 'exists' : 'null',
// infoBox: lastGeneratedData.infoBox ? 'exists' : 'null',
// characterThoughts: lastGeneratedData.characterThoughts ? 'exists' : 'null'
// });
committedTrackerData.userStats = lastGeneratedData.userStats;
committedTrackerData.infoBox = lastGeneratedData.infoBox;
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
// console.log('[RPG Companion] AFTER commit - committedTrackerData:', {
// userStats: committedTrackerData.userStats ? 'exists' : 'null',
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
// characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null'
// });
// Reset flag after committing (ready for next cycle)
} else {
// console.log('[RPG Companion] 🔄 SWIPE: Using existing committedTrackerData (no commit)');
// console.log('[RPG Companion] committedTrackerData:', {
// userStats: committedTrackerData.userStats ? 'exists' : 'null',
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
// characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null'
// });
// Reset flag after using it (swipe generation complete, ready for next action)
}
}
// For TOGETHER mode: Check if we need to commit extension data
// Only commit when user sends a new message (not on swipes)
if (extensionSettings.generationMode === 'together') {
if (!lastActionWasSwipe) {
// User sent a new message - commit data from the last assistant message they replied to
// This ensures swipes use consistent data from before the first swipe
console.log('[RPG Companion] 📝 TOGETHER MODE COMMIT: New message - committing from last assistant message');
// Find the last assistant message (before the user's new message)
@@ -163,79 +106,44 @@ export function onGenerationStarted(type, data) {
}
}
// Use the committed tracker data as source for generation
// console.log('[RPG Companion] Using committedTrackerData for generation');
// console.log('[RPG Companion] committedTrackerData.userStats:', committedTrackerData.userStats);
// Parse stats from committed data to update the extensionSettings for prompt generation
if (committedTrackerData.userStats) {
// console.log('[RPG Companion] Parsing committed userStats into extensionSettings');
parseUserStats(committedTrackerData.userStats);
// console.log('[RPG Companion] After parsing, extensionSettings.userStats:', JSON.stringify(extensionSettings.userStats));
}
if (extensionSettings.generationMode === 'together') {
// console.log('[RPG Companion] In together mode, generating prompts...');
const example = ''; // JSON format includes schema in instructions, no separate example needed
// Don't include HTML prompt in instructions - inject it separately to avoid duplication on swipes
const example = '';
const instructions = getTrackerInstructions(false, true);
// Clear separate mode context injection - we don't use contextual summary in together mode
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
// console.log('[RPG Companion] Example:', example ? 'exists' : 'empty');
// console.log('[RPG Companion] Chat length:', chat ? chat.length : 'chat is null');
// Find the last assistant message in the chat history
let lastAssistantDepth = -1; // -1 means not found
let lastAssistantDepth = -1;
if (chat && chat.length > 0) {
// console.log('[RPG Companion] Searching for last assistant message...');
// Start from depth 1 (skip depth 0 which is usually user's message or prefill)
for (let depth = 1; depth < chat.length; depth++) {
const index = chat.length - 1 - depth; // Convert depth to index
const index = chat.length - 1 - depth;
const message = chat[index];
// console.log('[RPG Companion] Checking depth', depth, 'index', index, 'message properties:', Object.keys(message));
// Check for assistant message: not user and not system
if (!message.is_user && !message.is_system) {
// Found assistant message at this depth
// Inject at the SAME depth to prepend to this assistant message
lastAssistantDepth = depth;
// console.log('[RPG Companion] Found last assistant message at depth', depth, '-> injecting at same depth:', lastAssistantDepth);
break;
}
}
}
// If we have previous tracker data and found an assistant message, inject it as an assistant message
if (!shouldSuppress && example && lastAssistantDepth > 0) {
setExtensionPrompt('rpg-companion-example', example, extension_prompt_types.IN_CHAT, lastAssistantDepth, false, extension_prompt_roles.ASSISTANT);
// console.log('[RPG Companion] Injected tracker example as assistant message at depth:', lastAssistantDepth);
} else {
// console.log('[RPG Companion] NOT injecting example. example:', !!example, 'lastAssistantDepth:', lastAssistantDepth);
}
// Inject the instructions as a user message at depth 0 (right before generation)
// If this is a guided generation (user explicitly injected 'instruct'), skip adding
// our tracker instructions to avoid clobbering the guided prompt.
if (!shouldSuppress) {
setExtensionPrompt('rpg-companion-inject', instructions, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER);
}
// console.log('[RPG Companion] Injected RPG tracking instructions at depth 0 (right before generation)');
// Inject HTML prompt separately at depth 0 if enabled (prevents duplication on swipes)
if (extensionSettings.enableHtmlPrompt && !shouldSuppress) {
// Use custom HTML prompt if set, otherwise use default
const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT;
const htmlPrompt = `\n${htmlPromptText}`;
setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false);
// console.log('[RPG Companion] Injected HTML prompt at depth 0 for together mode');
} else {
// Clear HTML prompt if disabled
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
}
} else if (extensionSettings.generationMode === 'separate') {
// In SEPARATE mode, inject the current state as JSON for main roleplay generation
const currentStateJSON = generateContextualSummary();
if (currentStateJSON) {
@@ -246,27 +154,18 @@ ${currentStateJSON}
\`\`\`
</context>\n\n`;
// Inject context at depth 1 (before last user message) as SYSTEM
// Skip when a guided generation injection is present to avoid conflicting instructions
if (!shouldSuppress) {
setExtensionPrompt('rpg-companion-context', wrappedContext, extension_prompt_types.IN_CHAT, 1, false);
}
// console.log('[RPG Companion] Injected current state JSON for separate mode:', currentStateJSON);
} else {
// Clear if no data yet
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
}
// Inject HTML prompt separately at depth 0 if enabled (same as together mode pattern)
if (extensionSettings.enableHtmlPrompt && !shouldSuppress) {
// Use custom HTML prompt if set, otherwise use default
const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT;
const htmlPrompt = `\n${htmlPromptText}`;
setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false);
// console.log('[RPG Companion] Injected HTML prompt at depth 0 for separate mode');
} else {
// Clear HTML prompt if disabled
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
}
+12 -23
View File
@@ -4,10 +4,10 @@
* Supports both legacy text format and new JSON format
*/
import { extensionSettings, FEATURE_FLAGS, addDebugLog, lastGeneratedData, committedTrackerData } from '../../core/state.js';
import { extensionSettings, addDebugLog, lastGeneratedData, committedTrackerData } from '../../core/state.js';
import { saveSettings, saveChatData } from '../../core/persistence.js';
import { extractInventory } from './inventoryParser.js';
import { validateTrackerData, mergeTrackerData } from '../../types/trackerData.js';
import { validateTrackerData } from '../../types/trackerData.js';
import { handleItemRemoved } from '../rendering/skills.js';
/**
@@ -850,24 +850,13 @@ export function parseUserStats(statsText) {
}
}
// Extract inventory - use v2 parser if feature flag enabled, otherwise fallback to v1
if (FEATURE_FLAGS.useNewInventory) {
const inventoryData = extractInventory(statsText);
if (inventoryData) {
extensionSettings.userStats.inventory = inventoryData;
debugLog('[RPG Parser] Inventory v2 extracted:', inventoryData);
} else {
debugLog('[RPG Parser] Inventory v2 extraction failed');
}
// Extract inventory - extractInventory() handles v2 format and falls back to v1 if needed
const inventoryData = extractInventory(statsText);
if (inventoryData) {
extensionSettings.userStats.inventory = inventoryData;
debugLog('[RPG Parser] Inventory extracted:', inventoryData);
} else {
// Legacy v1 parsing for backward compatibility
const inventoryMatch = statsText.match(/Inventory:\s*(.+)/i);
if (inventoryMatch) {
extensionSettings.userStats.inventory = inventoryMatch[1].trim();
debugLog('[RPG Parser] Inventory v1 extracted:', inventoryMatch[1].trim());
} else {
debugLog('[RPG Parser] Inventory v1 not found');
}
debugLog('[RPG Parser] Inventory extraction failed');
}
// Extract quests
@@ -900,7 +889,7 @@ export function parseUserStats(statsText) {
arousal: extensionSettings.userStats.arousal,
mood: extensionSettings.userStats.mood,
conditions: extensionSettings.userStats.conditions,
inventory: FEATURE_FLAGS.useNewInventory ? 'v2 object' : extensionSettings.userStats.inventory
inventory: extensionSettings.userStats.inventory
});
saveSettings();
@@ -947,11 +936,11 @@ export function parseSkills(skillsText) {
extensionSettings.skillAbilityLinks = {};
}
// Get configured skill categories (handle both old string and new object format)
// Migration function handles string array → object array conversion on load
const rawCategories = extensionSettings.trackerConfig?.userStats?.skillsSection?.customFields || [];
const configuredCategories = rawCategories
.filter(cat => typeof cat === 'string' || cat.enabled !== false)
.map(cat => typeof cat === 'string' ? cat : cat.name);
.filter(cat => cat.enabled !== false)
.map(cat => cat.name);
const lines = skillsText.split('\n');
const newSkillAbilityLinks = {};
+11 -13
View File
@@ -4,10 +4,9 @@
*/
import { getContext } from '../../../../../../extensions.js';
import { chat, getCurrentChatDetails, characters, this_chid } from '../../../../../../../script.js';
import { chat, characters, this_chid } from '../../../../../../../script.js';
import { selected_group, getGroupMembers, getGroupChat } from '../../../../../../group-chats.js';
import { extensionSettings, committedTrackerData } from '../../core/state.js';
import { generateSchemaExample } from '../../types/trackerData.js';
import { extensionSettings } from '../../core/state.js';
// Type imports
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
@@ -28,7 +27,7 @@ export const DEFAULT_JSON_TRACKER_PROMPT = `At the start of every reply, output
* Default message interception prompt text
* Guides the LLM to rewrite the user's message based on current RPG state and recent chat
*/
export const DEFAULT_MESSAGE_INTERCEPTION_PROMPT = `Act as an uncompromising Immersive Copy Editor who rewrites the user's draft to strictly adhere to {{user}}'s persona and RPG state (JSON). You must validate the feasibility of the user's intended actions for {{user}} against the JSON state; if the draft contradicts the state (e.g., acting smart while 'Intelligence' is low, or running while having a 'Leg Injury'), you are required to override the core intent, rewriting the action to portray immediate failure, struggle, or involuntary reaction instead of the user's desired success. Even further, if the intended course of action is physically impossible via the state or represents a thought process conceptually alien to the character's nature or current state, you are mandated to completely overwrite the user's intent. Aggressively rephrase vocabulary and syntax to match the character's specific cognitive capacity and tone. Keep the output concise and devoid of fluff; do not expand the narrative beyond the necessary state-enforced correction. Never include information that was not already present in the original draft. Never narrate the consequences of {{user}}'s actions, only what they are. Return ONLY the modified message text.`;
export const DEFAULT_MESSAGE_INTERCEPTION_PROMPT = `Act as an uncompromising Immersive Copy Editor who rewrites the user's draft to strictly adhere to {{user}}'s persona and RPG state (JSON). You must validate the feasibility of the user's intended actions for {{user}} to take against the JSON state; if the draft contradicts said state (e.g. acting smart while 'Intelligence' is low, or running while having a 'Leg Injury'), you are required to override the core intent, rewriting the action to portray immediate failure, struggle, or an involuntary reaction instead of the user's desired success. Even further, if the intended course of action is physically impossible via the state or represents a thought process that is conceptually alien to the character's nature or current state, you are mandated to completely overwrite the user's intent. Be careful not to confuse communicated intent (e.g. walk towards some direction, or throw a punch) with intended speech. Intent must always be overwritten with intent (e.g. if user wanted for {{user}} to run, but they have a leg injury, your correction will make them limp). Speech must always be overwritten with speech (e.g. if user means for {{user}} to speak eloquently, but they're not smart enough, you'd dumb down their choice of words or speech patterns). Never replace intent with failed speech (e.g. user communicates that {{user}} will throw a punch, or make a gesture, but you incorrectly decide that they will make muffled noises instead, as they are gagged). Aggressively rephrase vocabulary and syntax to match the character's specific cognitive capacity and tone. Keep the output concise and devoid of fluff; do not expand the narrative beyond the necessary state-enforced correction. Never include information that was not already present in the original draft. Never narrate the consequences of {{user}}'s actions, only what they are. Return ONLY the modified message text.`;
/**
* Gets character card information for current chat (handles both single and group chats)
@@ -362,16 +361,13 @@ export function generateJSONTrackerInstructions(includeHtmlPrompt = true, includ
// Skills section
if (showSkills) {
const skillCategories = trackerConfig?.userStats?.skillsSection?.customFields || [];
// Filter to only enabled categories and handle both old (string) and new (object) formats
const enabledCategories = skillCategories.filter(cat => {
if (typeof cat === 'string') return true;
return cat.enabled !== false;
});
// Migration function handles string array → object array conversion on load
const enabledCategories = skillCategories.filter(cat => cat.enabled !== false);
if (enabledCategories.length > 0) {
let skillsSection = ' "skills": {\n';
const categoryExamples = enabledCategories.map(cat => {
const catName = typeof cat === 'string' ? cat : cat.name;
const catName = cat.name;
let skillExample = '{ "name": "Ability Name", "description": "What this ability does" }';
if (enableItemSkillLinks) {
skillExample = '{ "name": "Ability", "description": "Description", "grantedBy": "Item Name" }';
@@ -407,6 +403,11 @@ export function generateJSONTrackerInstructions(includeHtmlPrompt = true, includ
instructions += '- Level is a numeric value (typically 1+, represents character progression)\n';
}
if (showQuests) {
instructions += '- A main quest can be created when the current main objective changes\n';
instructions += '- Optional quests can be created for smaller matters that need to be resolved\n';
}
instructions += '- Items should be placeed in the inventory section, not the skills section\n';
instructions += '- Characters should be removed as soon as they leave the scene\n';
instructions += '- Your list of characters must never include {{user}}\n';
@@ -675,8 +676,6 @@ export function generateContextualSummary() {
* @returns {string} Full prompt text for separate tracker generation
*/
export function generateRPGPromptText() {
const userName = getContext().name1;
let promptText = '';
promptText += `Here are the previous trackers in JSON format that you should consider when responding:\n`;
@@ -814,7 +813,6 @@ export function generateRPGPromptText() {
*/
export async function generateSeparateUpdatePrompt() {
const depth = extensionSettings.updateDepth;
const userName = getContext().name1;
const messages = [];