Implement {{charFirstMessage}} / {{greeting}} macro with alternate greeting indexing and substitution (#5220)

* Initial plan

* Implement {{firstMessage}} / {{greeting}} macro for character's first message

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* Simplify firstMessage resolver to use || instead of ?? for consistency

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* Remove {{firstMessage}} alias, add 0-based greeting index for alternate greetings

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* Add alternateGreetings to env, apply substitution to greetings

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* Remove legacy greeting macro, keep only new engine implementation

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>
Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com>
This commit is contained in:
Copilot
2026-03-01 17:44:29 +02:00
committed by GitHub
parent 63fa9c1d07
commit b5a1d227fd
5 changed files with 53 additions and 1 deletions
+15
View File
@@ -3230,6 +3230,8 @@ export function baseChatReplace(value, name1Override = null, name2Override = nul
* @property {string} version Character version * @property {string} version Character version
* @property {string} charDepthPrompt Character depth note * @property {string} charDepthPrompt Character depth note
* @property {string} creatorNotes Character creator notes * @property {string} creatorNotes Character creator notes
* @property {string} firstMessage Character first message / greeting
* @property {string[]} alternateGreetings Character alternate greetings
*/ */
/** /**
@@ -3316,6 +3318,17 @@ export function getCharacterCardFieldsLazy({ chid = undefined } = {}) {
const exampleDialog = chat_metadata.mes_example || character.mes_example || ''; const exampleDialog = chat_metadata.mes_example || character.mes_example || '';
return baseChatReplace(exampleDialog.trim()); return baseChatReplace(exampleDialog.trim());
}, },
firstMessage: () => {
if (!character) return '';
const firstMes = character.first_mes?.trim() || '';
return baseChatReplace(firstMes);
},
alternateGreetings: () => {
if (!character) return [];
const altGreetings = character.data?.alternate_greetings;
if (!Array.isArray(altGreetings)) return [];
return altGreetings.map(greeting => baseChatReplace(greeting?.trim()));
},
}; };
return createLazyFields(resolvers); return createLazyFields(resolvers);
@@ -3342,6 +3355,8 @@ export function getCharacterCardFields({ chid = undefined } = {}) {
version: lazy.version, version: lazy.version,
charDepthPrompt: lazy.charDepthPrompt, charDepthPrompt: lazy.charDepthPrompt,
creatorNotes: lazy.creatorNotes, creatorNotes: lazy.creatorNotes,
firstMessage: lazy.firstMessage,
alternateGreetings: lazy.alternateGreetings,
}; };
} }
@@ -140,6 +140,30 @@ export function registerEnvMacros() {
handler: ({ env }) => env.character.creatorNotes ?? '', handler: ({ env }) => env.character.creatorNotes ?? '',
}); });
MacroRegistry.registerMacro('charFirstMessage', {
aliases: [{ alias: 'greeting' }],
category: MacroCategory.CHARACTER,
unnamedArgs: [
{
name: 'index',
optional: true,
defaultValue: '0',
type: MacroValueType.INTEGER,
description: '0-based index. 0 (default) returns the main greeting, 1 and up return alternate greetings.',
},
],
description: 'The character\'s first message / greeting. Optionally specify an index to access alternate greetings.',
returns: 'Character greeting at the given index, or empty string if out of bounds.',
exampleUsage: ['{{greeting}}', '{{greeting::0}}', '{{greeting::1}}'],
handler: ({ env, unnamedArgs: [index] }) => {
const i = Number(index ?? 0);
if (i === 0) return env.character.firstMessage ?? '';
const altGreetings = env.character.alternateGreetings;
if (!Array.isArray(altGreetings)) return '';
return altGreetings[i - 1] ?? '';
},
});
// Character version macros (legacy variants and documented {{charVersion}}) // Character version macros (legacy variants and documented {{charVersion}})
MacroRegistry.registerMacro('charVersion', { MacroRegistry.registerMacro('charVersion', {
aliases: [ aliases: [
@@ -38,6 +38,8 @@
* @property {string} [charDepthPrompt] * @property {string} [charDepthPrompt]
* @property {string} [creatorNotes] * @property {string} [creatorNotes]
* @property {string} [version] * @property {string} [version]
* @property {string} [firstMessage]
* @property {string[]} [alternateGreetings]
*/ */
/** /**
@@ -109,10 +109,19 @@ class MacroEnvBuilder {
['version', 'version'], ['version', 'version'],
['charDepthPrompt', 'charDepthPrompt'], ['charDepthPrompt', 'charDepthPrompt'],
['creatorNotes', 'creatorNotes'], ['creatorNotes', 'creatorNotes'],
['firstMessage', 'firstMessage'],
['alternateGreetings', 'alternateGreetings'],
]); ]);
for (const [envKey, fieldKey] of fieldMappings) { for (const [envKey, fieldKey] of fieldMappings) {
Object.defineProperty(env.character, envKey, { Object.defineProperty(env.character, envKey, {
get() { return fields[fieldKey] || ''; }, get() {
const value = fields[fieldKey];
// alternateGreetings should default to [] instead of ''
if (envKey === 'alternateGreetings') {
return Array.isArray(value) ? value : [];
}
return value || '';
},
enumerable: true, enumerable: true,
configurable: true, configurable: true,
}); });
+2
View File
@@ -98,6 +98,8 @@ test.describe('MacroEnvBuilder', () => {
'version', 'version',
'charDepthPrompt', 'charDepthPrompt',
'creatorNotes', 'creatorNotes',
'firstMessage',
'alternateGreetings',
])); ]));
}); });