Expand WORLD_INFO_FORCE_ACTIVATE API to allow for dynamically modified Lorebook entries
| @@ -130,9 +130,9 @@ const KNOWN_DECORATORS = ['@@activate', '@@dont_activate']; | ||
| 130 | 130 | */ |
| 131 | 131 | class WorldInfoBuffer { |
| 132 | 132 | /** |
| 133 | 133 | * @type {Map<string, object[]>} ArrayMap of entries that need to be activated no matter what |
| 134 | 134 | */ |
| 135 | 135 | static externalActivations = []new Map(); |
| 136 | 136 | |
| 137 | 137 | /** |
| 138 | 138 | * @type {string[]} Array of messages sorted by ascending depth |
| @@ -311,20 +311,20 @@ class WorldInfoBuffer { | ||
| 311 | 311 | } |
| 312 | 312 | |
| 313 | 313 | /** |
| 314 | 314 | * Check ifGet the currentexternally activated version of the entry, isif externallythere activatedis one. |
| 315 | 315 | * @param {object} entry WI entry to check |
| 316 | 316 | * @returns {booleanobject|null} Truethe external version if the entry is forcefully activated, null otherwise |
| 317 | 317 | */ |
| 318 | 318 | isExternallyActivatedgetExternallyActivated(entry) { |
| 319 | 319 | // Entries could be copied with structuredClone, so we need to compare them by string representation |
| 320 | - return WorldInfoBuffer.externalActivations.some(x => JSON.stringify(x) === JSON.stringify(entry)); | |
| 320 | + return WorldInfoBuffer.externalActivations.get(`${entry.world}.${entry.uid}`); | |
| 321 | 321 | } |
| 322 | 322 | |
| 323 | 323 | /** |
| 324 | 324 | * Clean-up the external effects for entries. |
| 325 | 325 | */ |
| 326 | 326 | resetExternalEffects() { |
| 327 | - WorldInfoBuffer.externalActivations.splice(0, WorldInfoBuffer.externalActivations.length); | |
| 327 | + WorldInfoBuffer.externalActivations = new Map(); | |
| 328 | 328 | } |
| 329 | 329 | |
| 330 | 330 | /** |
| @@ -751,7 +751,7 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) { | ||
| 751 | 751 | worldInfoString = worldInfoBefore + worldInfoAfter; |
| 752 | 752 | |
| 753 | 753 | if (!isDryRun && activatedWorldInfo.allActivatedEntries && activatedWorldInfo.allActivatedEntries.size > 0) { |
| 754 | 754 | const arg = Array.from(activatedWorldInfo.allActivatedEntries.values()); |
| 755 | 755 | await eventSource.emit(event_types.WORLD_INFO_ACTIVATED, arg); |
| 756 | 756 | } |
| 757 | 757 | |
| @@ -868,7 +868,13 @@ export function setWorldInfoSettings(settings, data) { | ||
| 868 | 868 | }); |
| 869 | 869 | |
| 870 | 870 | eventSource.on(event_types.WORLDINFO_FORCE_ACTIVATE, (entries) => { |
| 871 | - WorldInfoBuffer.externalActivations.push(...entries); | |
| 871 | + for (const entry of entries) { | |
| 872 | + if (!Object.hasOwn(entry, 'world') || !Object.hasOwn(entry, 'uid')) { | |
| 873 | + console.error('WORLDINFO_FORCE_ACTIVATE requires all entries to have both world and uid fields, entry IGNORED'); | |
| 874 | + } else { | |
| 875 | + WorldInfoBuffer.externalActivations.set(`${entry.world}.${entry.uid}`, entry); | |
| 876 | + } | |
| 877 | + } | |
| 872 | 878 | }); |
| 873 | 879 | |
| 874 | 880 | // Add slash commands |
| @@ -3698,7 +3704,7 @@ function parseDecorators(content) { | ||
| 3698 | 3704 | * @param {string[]} chat The chat messages to scan, in reverse order. |
| 3699 | 3705 | * @param {number} maxContext The maximum context size of the generation. |
| 3700 | 3706 | * @param {boolean} isDryRun Whether to perform a dry run. |
| 3701 | 3707 | * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: SetMap<string, any> }} WIActivated |
| 3702 | 3708 | * @returns {Promise<WIActivated>} The world info activated. |
| 3703 | 3709 | */ |
| 3704 | 3710 | export async function checkWorldInfo(chat, maxContext, isDryRun) { |
| @@ -3724,7 +3730,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3724 | 3730 | let scanState = scan_state.INITIAL; |
| 3725 | 3731 | let token_budget_overflowed = false; |
| 3726 | 3732 | let count = 0; |
| 3727 | 3733 | let allActivatedEntries = new SetMap(); |
| 3728 | 3734 | let failedProbabilityChecks = new Set(); |
| 3729 | 3735 | let allActivatedText = ''; |
| 3730 | 3736 | |
| @@ -3742,7 +3748,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3742 | 3748 | !isDryRun && timedEffects.checkTimedEffects(); |
| 3743 | 3749 | |
| 3744 | 3750 | if (sortedEntries.length === 0) { |
| 3745 | 3751 | return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new SetMap() }; |
| 3746 | 3752 | } |
| 3747 | 3753 | |
| 3748 | 3754 | /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */ |
| @@ -3789,7 +3795,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3789 | 3795 | } |
| 3790 | 3796 | |
| 3791 | 3797 | // Already processed, considered and then skipped entries should still be skipped |
| 3792 | 3798 | if (failedProbabilityChecks.has(entry) || allActivatedEntries.has(`${entry.world}.${entry.uid}`)) { |
| 3793 | 3799 | continue; |
| 3794 | 3800 | } |
| 3795 | 3801 | |
| @@ -3858,6 +3864,12 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3858 | 3864 | continue; |
| 3859 | 3865 | } |
| 3860 | 3866 | |
| 3867 | + if (buffer.getExternallyActivated(entry)) { | |
| 3868 | + log('externally activated'); | |
| 3869 | + activatedNow.add(buffer.getExternallyActivated(entry)); | |
| 3870 | + continue; | |
| 3871 | + } | |
| 3872 | + | |
| 3861 | 3873 | if (entry.decorators.includes('@@activate')) { |
| 3862 | 3874 | log('activated by @@activate decorator'); |
| 3863 | 3875 | activatedNow.add(entry); |
| @@ -3876,12 +3888,6 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3876 | 3888 | continue; |
| 3877 | 3889 | } |
| 3878 | 3890 | |
| 3879 | - if (buffer.isExternallyActivated(entry)) { | |
| 3880 | - log('externally activated'); | |
| 3881 | - activatedNow.add(entry); | |
| 3882 | - continue; | |
| 3883 | - } | |
| 3884 | - | |
| 3885 | 3891 | if (isSticky) { |
| 3886 | 3892 | log('activated because active sticky'); |
| 3887 | 3893 | activatedNow.add(entry); |
| @@ -4039,7 +4045,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4039 | 4045 | break; |
| 4040 | 4046 | } |
| 4041 | 4047 | |
| 4042 | - allActivatedEntries.add(entry); | |
| 4048 | + allActivatedEntries.set(`${entry.world}.${entry.uid}`, entry); | |
| 4043 | 4049 | console.debug(`[WI] Entry ${entry.uid} activation successful, adding to prompt`, entry); |
| 4044 | 4050 | } |
| 4045 | 4051 | |
| @@ -4123,7 +4129,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4123 | 4129 | |
| 4124 | 4130 | // Appends from insertion order 999 to 1. Use unshift for this purpose |
| 4125 | 4131 | // TODO (kingbri): Change to use WI Anchor positioning instead of separate top/bottom arrays |
| 4126 | 4132 | [...allActivatedEntries.values()].sort(sortFn).forEach((entry) => { |
| 4127 | 4133 | const regexDepth = entry.position === world_info_position.atDepth ? (entry.depth ?? DEFAULT_DEPTH) : null; |
| 4128 | 4134 | const content = getRegexedString(entry.content, regex_placement.WORLD_INFO, { depth: regexDepth, isMarkdown: false, isPrompt: true }); |
| 4129 | 4135 | |
| @@ -4182,14 +4188,14 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4182 | 4188 | context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]); |
| 4183 | 4189 | } |
| 4184 | 4190 | |
| 4185 | 4191 | !isDryRun && timedEffects.setTimedEffects(Array.from(allActivatedEntries.values())); |
| 4186 | 4192 | buffer.resetExternalEffects(); |
| 4187 | 4193 | timedEffects.cleanUp(); |
| 4188 | 4194 | |
| 4189 | 4195 | console.log(`[WI] Adding ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values())); |
| 4190 | 4196 | console.debug('[WI] --- DONE ---'); |
| 4191 | 4197 | |
| 4192 | 4198 | return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: allActivatedEntries.values() }; |
| 4193 | 4199 | } |
| 4194 | 4200 | |
| 4195 | 4201 | /** |
| @@ -4291,7 +4297,7 @@ function filterGroupsByTimedEffects(groups, timedEffects, removeEntry) { | ||
| 4291 | 4297 | /** |
| 4292 | 4298 | * Filters entries by inclusion groups. |
| 4293 | 4299 | * @param {object[]} newEntries Entries activated on current recursion level |
| 4294 | 4300 | * @param {SetMap<string, object>} allActivatedEntries SetMap of all activated entries |
| 4295 | 4301 | * @param {WorldInfoBuffer} buffer The buffer to use for scanning |
| 4296 | 4302 | * @param {number} scanState The current scan state |
| 4297 | 4303 | * @param {WorldInfoTimedEffects} timedEffects The timed effects currently active |
| @@ -4339,7 +4345,7 @@ function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanSt | ||
| 4339 | 4345 | continue; |
| 4340 | 4346 | } |
| 4341 | 4347 | |
| 4342 | 4348 | if (Array.from(allActivatedEntries.values()).some(x => x.group === key)) { |
| 4343 | 4349 | console.debug(`[WI] Skipping inclusion group check, group '${key}' was already activated`); |
| 4344 | 4350 | // We need to forcefully deactivate all other entries in the group |
| 4345 | 4351 | removeAllBut(group, null, false); |