Fix tracker injection context ordering and placeholder timing
This commit is contained in:
+44
-7
@@ -556,6 +556,38 @@ export function getSwipeData(message, swipeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve active swipe index for a message.
|
||||
* Falls back to message.swipe_id, but prefers exact match against current
|
||||
* message text when available to avoid stale swipe_id during event timing races.
|
||||
*
|
||||
* @param {Object} message - Assistant message object
|
||||
* @returns {number} Active swipe index
|
||||
*/
|
||||
function resolveActiveSwipeId(message) {
|
||||
const fallbackSwipeId = Number(message?.swipe_id ?? 0);
|
||||
const swipes = Array.isArray(message?.swipes) ? message.swipes : null;
|
||||
|
||||
if (!swipes || swipes.length === 0) {
|
||||
return Math.max(0, fallbackSwipeId);
|
||||
}
|
||||
|
||||
const currentText = typeof message?.mes === 'string' ? message.mes : '';
|
||||
if (currentText) {
|
||||
for (let i = swipes.length - 1; i >= 0; i--) {
|
||||
if (typeof swipes[i] === 'string' && swipes[i] === currentText) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackSwipeId < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.min(fallbackSwipeId, swipes.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits tracker data from the assistant message immediately before currentMessageIndex.
|
||||
* Walks backward through the chat skipping the current message, user messages, and system
|
||||
@@ -574,25 +606,30 @@ export function commitTrackerDataFromPriorMessage(currentMessageIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('[RPG Companion] commitTrackerDataFromPriorMessage called with index', currentMessageIndex, '| chat.length =', chat.length);
|
||||
|
||||
for (let i = currentMessageIndex - 1; i >= 0; i--) {
|
||||
const message = chat[i];
|
||||
if (message.is_user || message.is_system) continue;
|
||||
|
||||
// Found the prior assistant message — commit its active swipe data
|
||||
const swipeId = message.swipe_id || 0;
|
||||
const swipeId = resolveActiveSwipeId(message);
|
||||
const swipeData = getSwipeData(message, swipeId);
|
||||
// console.log('[RPG Companion] Committing from chat[' + i + '] swipe', swipeId, '| has swipe data:', !!swipeData);
|
||||
committedTrackerData.userStats = swipeData?.userStats || null;
|
||||
committedTrackerData.infoBox = swipeData?.infoBox || null;
|
||||
const rawCharacterThoughts = swipeData?.characterThoughts;
|
||||
|
||||
if (!swipeData) {
|
||||
// Keep searching backward for a valid state if this assistant message has no data
|
||||
continue;
|
||||
}
|
||||
|
||||
committedTrackerData.userStats = swipeData.userStats || null;
|
||||
committedTrackerData.infoBox = swipeData.infoBox || null;
|
||||
const rawCharacterThoughts = swipeData.characterThoughts;
|
||||
committedTrackerData.characterThoughts =
|
||||
rawCharacterThoughts == null
|
||||
? null
|
||||
: (typeof rawCharacterThoughts === 'string'
|
||||
? rawCharacterThoughts
|
||||
: JSON.stringify(rawCharacterThoughts));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -631,7 +668,7 @@ export function inheritSwipeDataFromPriorMessage(message, messageIndex) {
|
||||
const msg = chat[i];
|
||||
if (msg.is_user || msg.is_system) continue;
|
||||
|
||||
const swipeId = msg.swipe_id || 0;
|
||||
const swipeId = resolveActiveSwipeId(msg);
|
||||
const swipeData = getSwipeData(msg, swipeId);
|
||||
if (!swipeData) continue; // No data on this assistant message; keep searching further back
|
||||
|
||||
|
||||
@@ -38,8 +38,9 @@ let currentSuppressionState = false;
|
||||
// Type imports
|
||||
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
||||
|
||||
// Track last chat length we committed at to prevent duplicate commits from streaming
|
||||
let lastCommittedChatLength = -1;
|
||||
// Track the latest user message we committed for to prevent duplicate commits
|
||||
// when GENERATION_STARTED can fire multiple times for the same turn.
|
||||
let lastCommittedUserMessageSignature = null;
|
||||
|
||||
// Store context map for prompt injection (used by event handlers)
|
||||
let pendingContextMap = new Map();
|
||||
@@ -607,66 +608,12 @@ export async function onGenerationStarted(type, data, dryRun) {
|
||||
// Ensure checkpoint is applied before generation
|
||||
await restoreCheckpointOnLoad();
|
||||
|
||||
const currentChatLength = chat ? chat.length : 0;
|
||||
|
||||
// For TOGETHER mode: Commit when user sends message (before first generation)
|
||||
if (extensionSettings.generationMode === 'together') {
|
||||
// By the time onGenerationStarted fires, ST has already added the placeholder AI message
|
||||
// So we check the second-to-last message to see if user just sent a message
|
||||
const secondToLastMessage = chat && chat.length > 1 ? chat[chat.length - 2] : null;
|
||||
const isUserMessage = secondToLastMessage && secondToLastMessage.is_user;
|
||||
|
||||
// Commit if:
|
||||
// 1. Second-to-last message is from USER (user just sent message)
|
||||
// 2. Not a swipe (lastActionWasSwipe = false)
|
||||
// 3. Haven't already committed for this chat length (prevent streaming duplicates)
|
||||
const shouldCommit = isUserMessage && !lastActionWasSwipe && currentChatLength !== lastCommittedChatLength;
|
||||
|
||||
if (shouldCommit) {
|
||||
// console.log('[RPG Companion] 📝 TOGETHER MODE COMMIT: User sent message - committing from N-1 assistant message');
|
||||
// console.log('[RPG Companion] Chat length:', currentChatLength, 'Last committed:', lastCommittedChatLength);
|
||||
|
||||
// Commit from the prior assistant message's swipe store (N-1 rule).
|
||||
// currentChatLength - 1 is the new AI placeholder; the function walks backward
|
||||
// past it and the user message to find the previous AI message's tracker state.
|
||||
commitTrackerDataFromPriorMessage(currentChatLength - 1);
|
||||
|
||||
// Track chat length to prevent duplicate commits from streaming
|
||||
lastCommittedChatLength = currentChatLength;
|
||||
|
||||
// console.log('[RPG Companion] AFTER: committedTrackerData =', {
|
||||
// userStats: committedTrackerData.userStats ? `${committedTrackerData.userStats.substring(0, 50)}...` : 'null',
|
||||
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
|
||||
// characterThoughts: committedTrackerData.characterThoughts ? `${committedTrackerData.characterThoughts.substring(0, 100)}...` : 'null'
|
||||
// });
|
||||
} else if (lastActionWasSwipe) {
|
||||
// console.log('[RPG Companion] ⏭️ Skipping commit: swipe (using previous committed data)');
|
||||
} else if (!isUserMessage) {
|
||||
// console.log('[RPG Companion] ⏭️ Skipping commit: second-to-last message is not user message (likely swipe or continuation)');
|
||||
}
|
||||
|
||||
// console.log('[RPG Companion] 📦 TOGETHER MODE: Injecting committed tracker data into prompt');
|
||||
// console.log('[RPG Companion] committedTrackerData =', {
|
||||
// userStats: committedTrackerData.userStats ? `${committedTrackerData.userStats.substring(0, 50)}...` : 'null',
|
||||
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
|
||||
// characterThoughts: committedTrackerData.characterThoughts ? `${committedTrackerData.characterThoughts.substring(0, 100)}...` : 'null'
|
||||
// });
|
||||
}
|
||||
|
||||
// For SEPARATE and EXTERNAL modes: 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' || extensionSettings.generationMode === 'external') && !isGenerating) {
|
||||
if (!lastActionWasSwipe) {
|
||||
// User sent a new message - commit from the prior assistant message's swipe store
|
||||
// (N-1 rule) rather than lastGeneratedData, which may reflect a sibling swipe's
|
||||
// outcome and would poison the context for the new generation.
|
||||
// currentChatLength - 1 is the new AI placeholder; search starts before it.
|
||||
commitTrackerDataFromPriorMessage(currentChatLength - 1);
|
||||
}
|
||||
// If lastActionWasSwipe, context was already committed by commitTrackerDataFromPriorMessage
|
||||
// in onMessageSwiped before generation started.
|
||||
// If this is a new generation (not a swipe and not the tracker update pass),
|
||||
// commit the tracker data from the last assistant message (N-1 rule).
|
||||
// Passing chat.length ensures we start searching backwards from the end of the chat,
|
||||
// correctly finding the latest valid assistant state regardless of where the user message is.
|
||||
if (!lastActionWasSwipe && !isGenerating) {
|
||||
commitTrackerDataFromPriorMessage(chat ? chat.length : 0);
|
||||
}
|
||||
|
||||
// Use the committed tracker data as source for generation
|
||||
|
||||
@@ -231,6 +231,38 @@ function getCurrentSwipeText(message) {
|
||||
return typeof message?.mes === 'string' ? message.mes : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the currently active swipe index for a message.
|
||||
* Some ST flows can briefly expose a stale message.swipe_id during swipe transitions,
|
||||
* so we also match against message.mes in the swipes array when possible.
|
||||
*
|
||||
* @param {Object} message - Assistant message object
|
||||
* @returns {number} Active swipe index
|
||||
*/
|
||||
function resolveActiveSwipeId(message) {
|
||||
const fallbackSwipeId = Number(message?.swipe_id ?? 0);
|
||||
const swipes = Array.isArray(message?.swipes) ? message.swipes : null;
|
||||
|
||||
if (!swipes || swipes.length === 0) {
|
||||
return Math.max(0, fallbackSwipeId);
|
||||
}
|
||||
|
||||
const currentText = typeof message?.mes === 'string' ? message.mes : '';
|
||||
if (currentText) {
|
||||
for (let i = swipes.length - 1; i >= 0; i--) {
|
||||
if (typeof swipes[i] === 'string' && swipes[i] === currentText) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackSwipeId < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.min(fallbackSwipeId, swipes.length - 1);
|
||||
}
|
||||
|
||||
function repairLatestTrackerStateFromCurrentSwipeContent(chatMessages = getContext()?.chat || []) {
|
||||
for (let i = chatMessages.length - 1; i >= 0; i--) {
|
||||
const message = chatMessages[i];
|
||||
@@ -393,6 +425,7 @@ export function onMessageSent() {
|
||||
const chat = context.chat;
|
||||
const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null;
|
||||
|
||||
|
||||
if (lastMessage && lastMessage.mes === '...') {
|
||||
// console.log('[RPG Companion] 🟢 Ignoring onMessageSent: streaming placeholder message');
|
||||
return;
|
||||
@@ -405,6 +438,9 @@ export function onMessageSent() {
|
||||
// This allows auto-update to distinguish between new generations and loading chat history
|
||||
setIsAwaitingNewMessage(true);
|
||||
|
||||
|
||||
|
||||
|
||||
// Note: FAB spinning is NOT shown for together mode since no extra API request is made
|
||||
// The RPG data comes embedded in the main response
|
||||
// FAB spinning is handled by apiClient.js for separate/external modes when updateRPGData() is called
|
||||
@@ -430,6 +466,7 @@ export async function onMessageReceived(data) {
|
||||
// Commit happens in onMessageSent (when user sends message, before generation)
|
||||
const lastMessage = chat[chat.length - 1];
|
||||
if (lastMessage && !lastMessage.is_user) {
|
||||
const rawSwipeId = Number(lastMessage.swipe_id ?? 0);
|
||||
const responseText = lastMessage.mes;
|
||||
const parsedData = parseResponse(responseText);
|
||||
|
||||
@@ -471,7 +508,8 @@ export async function onMessageReceived(data) {
|
||||
lastMessage.extra.rpg_companion_swipes = {};
|
||||
}
|
||||
|
||||
const currentSwipeId = lastMessage.swipe_id || 0;
|
||||
const currentSwipeId = resolveActiveSwipeId(lastMessage);
|
||||
|
||||
setMessageSwipeTrackerData(lastMessage, currentSwipeId, {
|
||||
userStats: parsedData.userStats,
|
||||
infoBox: parsedData.infoBox,
|
||||
@@ -668,7 +706,7 @@ export function onMessageSwiped(messageIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSwipeId = message.swipe_id || 0;
|
||||
const currentSwipeId = resolveActiveSwipeId(message);
|
||||
const swipeCount = Array.isArray(message.swipes) ? message.swipes.length : 0;
|
||||
|
||||
// Only set flag to true if this swipe will trigger a NEW generation
|
||||
@@ -677,7 +715,7 @@ export function onMessageSwiped(messageIndex) {
|
||||
message.swipes[currentSwipeId] !== undefined &&
|
||||
message.swipes[currentSwipeId] !== null &&
|
||||
message.swipes[currentSwipeId].length > 0;
|
||||
const swipeData = getCurrentSwipeTrackerData(message);
|
||||
const swipeData = getSwipeData(message, currentSwipeId);
|
||||
const isPendingNewSwipe = currentSwipeId >= swipeCount;
|
||||
|
||||
if (!isExistingSwipe) {
|
||||
|
||||
Reference in New Issue
Block a user