Environment
🐧 Linux
System
NixOS, Firefox
Version
1.17.0 staging
Desktop Information
No response
Describe the problem
Macros with side effects (e.g. {{setvar::x::...}}) placed in greetings are executed for every greeting on every generation, not just the selected one. This makes it impossible to use per-greeting variable definitions.
Steps to reproduce:
- Create a character whose first message contains
{{setvar::route::A}} and an alternate greeting containing {{setvar::route::B}}.
- Start a chat and swipe to the alternate greeting (
route should now be B).
- Send a message so a generation runs, then check
{{getvar::route}}.
Expected: route is B, the value set by the greeting actually in use.
Actual: route is A. Every generation re-runs the setvars from first_mes (and all alternate greetings), clobbering the value from the selected greeting.
Root cause: getCharacterCardFields() in public/script.js eagerly resolves every field returned by getCharacterCardFieldsLazy(), including firstMessage and alternateGreetings. Resolving those runs baseChatReplace over first_mes and every alternate greeting, which fires their macro side effects. Since getCharacterCardFields() is called on each generation, the side effects of unselected greetings run repeatedly, overwriting chat variables set by the active greeting.
Additional info
Here's what my clanker vibeslopped to fix it.
diff --git a/public/script.js b/public/script.js
index 2620fe858..f06c3a2e0 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3416,8 +3416,8 @@ export function getCharacterCardFieldsLazy({ chid = undefined } = {}) {
export function getCharacterCardFields({ chid = undefined } = {}) {
const lazy = getCharacterCardFieldsLazy({ chid });
- // Resolve all lazy fields into a plain object
- return {
+ // Resolve the non-greeting fields eagerly into a plain object.
+ const fields = {
system: lazy.system,
mesExamples: lazy.mesExamples,
description: lazy.description,
@@ -3428,9 +3428,26 @@ export function getCharacterCardFields({ chid = undefined } = {}) {
version: lazy.version,
charDepthPrompt: lazy.charDepthPrompt,
creatorNotes: lazy.creatorNotes,
- firstMessage: lazy.firstMessage,
- alternateGreetings: lazy.alternateGreetings,
};
+
+ // Keep the greeting fields lazy. Resolving them runs baseChatReplace over
+ // first_mes and *every* alternate greeting, so eagerly resolving here would
+ // fire the {{setvar::}} side-effects of unselected greetings on every call
+ // (getCharacterCardFields runs on each generation), clobbering chat
+ // variables set by the active greeting. They are only resolved on explicit
+ // access, e.g. via the {{greeting}} macro.
+ Object.defineProperty(fields, 'firstMessage', {
+ get: () => lazy.firstMessage,
+ enumerable: true,
+ configurable: true,
+ });
+ Object.defineProperty(fields, 'alternateGreetings', {
+ get: () => lazy.alternateGreetings,
+ enumerable: true,
+ configurable: true,
+ });
+
+ return /** @type {CharacterCardFields} */ (fields);
}
/**
Note this patch is a quick, sloppy fix. The getters still resolve (and fire side effects) if a caller spreads or serializes the object. A more thorough fix would be to audit the getCharacterCardFields call sites and switch the ones that don't need resolved greetings to getCharacterCardFieldsLazy, so greeting macros only run when a greeting is actually rendered.
Please tick the boxes
Environment
🐧 Linux
System
NixOS, Firefox
Version
1.17.0 staging
Desktop Information
No response
Describe the problem
Macros with side effects (e.g.
{{setvar::x::...}}) placed in greetings are executed for every greeting on every generation, not just the selected one. This makes it impossible to use per-greeting variable definitions.Steps to reproduce:
{{setvar::route::A}}and an alternate greeting containing{{setvar::route::B}}.routeshould now beB).{{getvar::route}}.Expected:
routeisB, the value set by the greeting actually in use.Actual:
routeisA. Every generation re-runs the setvars fromfirst_mes(and all alternate greetings), clobbering the value from the selected greeting.Root cause:
getCharacterCardFields()inpublic/script.jseagerly resolves every field returned bygetCharacterCardFieldsLazy(), includingfirstMessageandalternateGreetings. Resolving those runsbaseChatReplaceoverfirst_mesand every alternate greeting, which fires their macro side effects. SincegetCharacterCardFields()is called on each generation, the side effects of unselected greetings run repeatedly, overwriting chat variables set by the active greeting.Additional info
Here's what my clanker vibeslopped to fix it.
Note this patch is a quick, sloppy fix. The getters still resolve (and fire side effects) if a caller spreads or serializes the object. A more thorough fix would be to audit the getCharacterCardFields call sites and switch the ones that don't need resolved greetings to getCharacterCardFieldsLazy, so greeting macros only run when a greeting is actually rendered.
Please tick the boxes