宏与变量
注册自定义宏、对文本求值宏、读写聊天作用域或全局变量的相关 API。
宏
Luker 通过 macros 命名空间提供宏系统,同时为向后兼容保留了 MacrosParser。{{user}}、{{char}}、{{lastMessage}}、{{getvar::name}} 等内置宏由核心注册;插件可以通过 macros.register() 添加自己的宏。
macros.register
macros.register(name: string, options: {
handler: (ctx: MacroExecutionContext) => string,
aliases?: { alias: string, visible?: boolean }[],
category?: string,
unnamedArgs?: number | UnnamedArgDef[],
list?: boolean | { min: number, max?: number },
strictArgs?: boolean,
description?: string,
returns?: string,
returnType?: 'string' | 'integer' | 'number' | 'boolean',
displayOverride?: string,
exampleUsage?: string | string[],
delayArgResolution?: boolean,
}): MacroDefinition | null注册宏。返回注册成功的定义;校验失败则返回 null。
| 选项 | 说明 |
|---|---|
handler | 宏主体。接收带解析后参数的执行上下文 |
aliases | 备用名。每个 { alias, visible } 注册同一处理器 |
category | 自动补全里的分组(如 'utility'、'character'、'time') |
unnamedArgs | 数量(全部必填)或参数定义数组 |
list | 是否接受可变长度的参数列表 |
strictArgs | 为 false 时,arity / 类型不匹配只 log 警告而不抛错 |
delayArgResolution | 为 true 时,参数中的嵌套宏不会被预先解析——处理器必须自己调用 ctx.resolve(text)。仅用于控制流式宏 |
处理器上下文
处理器收到 MacroExecutionContext,包含:
| 字段 | 说明 |
|---|---|
name | 调用时使用的宏名 |
args | 命名参数的值 |
unnamedArgs | 非命名位置参数 |
list | 可变长度的列表参数 |
env | 宏求值环境(聊天、角色、persona 等) |
normalize(value) | 把值强制为该宏的返回类型 |
trimContent(content, opts?) | 修剪多行块 |
resolve(text, opts?) | 解析 text 中的嵌套宏 |
warn(message, error?) | 记录归属于此宏的警告 |
const ctx = Luker.getContext();
ctx.macros.register('myStatus', {
description: 'Returns the plugin status string.',
category: 'utility',
handler: () => 'My plugin is active.',
});
ctx.macros.register('greet', {
description: 'Greets a name.',
unnamedArgs: [
{ name: 'name', optional: false, type: 'string', description: 'Person to greet' },
],
handler: (mctx) => `Hello, ${mctx.unnamedArgs[0]}!`,
});注册后,{{myStatus}} 和 {{greet::Bob}} 都能工作。
macros.registry
底层的注册表。可用于反注册和检视:
ctx.macros.registry.unregisterMacro('myStatus');
ctx.macros.registry.hasMacro('greet');
const def = ctx.macros.registry.getMacro('greet');内置宏参考
下面是核心注册的宏的非穷尽列表。完整覆盖请见 public/scripts/macros/definitions/ 下的源码。
| 宏 | 返回 |
|---|---|
{{user}} | 当前用户 / persona 名 |
{{char}} | 当前角色名 |
{{persona}} | 当前 persona 描述 |
{{charDescription}} / {{charPersonality}} / {{charScenario}} | 卡片字段 |
{{charDepthPrompt}} / {{charCreatorNotes}} / {{charFirstMessage}} / {{charVersion}} | 卡片字段 |
{{mesExamples}} / {{mesExamplesRaw}} | 对话示例 |
{{group}} / {{groupNotMuted}} | 群组成员名 |
{{lastMessage}} / {{lastMessageId}} / {{lastUserMessage}} / {{lastCharMessage}} | 最近聊天内容 |
{{firstIncludedMessageId}} / {{firstDisplayedMessageId}} | 可见性窗口 |
{{lastSwipeId}} / {{currentSwipeId}} | swipe 状态 |
{{model}} | 当前模型标识 |
{{maxPrompt}} / {{maxContext}} / {{maxResponse}} | token 预算 |
{{time}} / {{date}} / {{weekday}} / {{isotime}} / {{isodate}} | 本地时钟 |
{{datetimeformat::FORMAT}} | moment.format(FORMAT) |
{{idleDuration}} / {{timeDiff}} | 时间差 |
{{getvar::name}} / {{setvar::name::value}} / {{addvar::name::value}} | 本地变量 |
{{incvar::name}} / {{decvar::name}} / {{hasvar::name}} / {{deletevar::name}} | 本地变量 |
{{pushvar::name::value}} / {{popvar::name}} | 本地变量——数组 push / pop。缺失时自动建为 [];支持点号路径 |
{{getglobalvar::name}} / {{setglobalvar::name::value}} / ... | 全局变量 |
{{if::cond::then::else}} / {{else::...}} / {{each::...}} | 控制流 |
{{trim}} / {{newline}} / {{space}} / {{noop}} | 空白辅助 |
{{roll::XdY}} / {{random::a,b,c}} / {{pick::a,b,c}} | 随机 |
{{//comment}} | 注释(输出忽略) |
{{outlet::name}} | 自定义 WI outlet 内容 |
{{isMobile}} / {{hasExtension::name}} | 环境检查 |
{{lastGenerationType}} / {{systemPrompt}} | 流水线状态 |
MacrosParser(已弃用)
MacrosParser.registerMacro(key: string, value: string | (nonce) => string, description?: string): void
MacrosParser.unregisterMacro(key: string): void旧版的简单字符串替换宏注册 API。会记录弃用警告。请迁移到 macros.register({ handler }) 以获得完整能力,或在一次性调用中给 substituteParams 传 dynamicMacros。
substituteParams
substituteParams(content: string, options?: {
name1Override?: string,
name2Override?: string,
original?: string,
groupOverride?: string,
replaceCharacterCard?: boolean,
dynamicMacros?: Record<string, string | (() => string)>,
postProcessFn?: (text: string) => string,
}): string解析 content 中的所有宏。dynamicMacros 可以为单次调用注入临时的零参数宏:
const result = ctx.substituteParams('Hello, {{user}}! Today is {{date}}.');substituteParamsExtended
substituteParamsExtended(
content: string,
additionalMacros?: Record<string, string | (() => string)>,
postProcessFn?: (text: string) => string,
): stringsubstituteParams 的便捷封装,仅为本次调用添加 additionalMacros:
const result = ctx.substituteParamsExtended(
'Query: {{queryText}}',
{ queryText: userInput },
);additionalMacros 不会被全局注册——它们只对这一次替换有效。
变量
有两种作用域可用:本地(按聊天,持久化在 chat_metadata.variables)和全局(跨聊天,持久化在 extension_settings.variables.global)。
本地变量
context.variables.local.get(name: string, args?: object): string | number
context.variables.local.set(name: string, value: any, args?: object): any
context.variables.local.add(name: string, value: any): any
context.variables.local.inc(name: string): any
context.variables.local.dec(name: string): any
context.variables.local.del(name: string): ''
context.variables.local.has(name: string): boolean
context.variables.local.push(name: string, value: any): string | undefined
context.variables.local.pop(name: string): string | undefined| 方法 | 说明 |
|---|---|
get | 读取变量。数字字符串自动强制为数字。不存在时返回 '' |
set | 写入变量。返回值 |
add | 两者都是数字时执行加法。已有值是 JSON 数组时 push。否则按字符串拼接 |
inc / dec | add(name, ±1) 的快捷方式 |
del | 删除变量。返回 '' |
has | 布尔型存在性检查 |
push | 把 value 推入 name 处的 JSON 数组。缺失时自动建为 []。对应宏形式 {{pushvar::name::value}} |
pop | 从 name 处的 JSON 数组弹出最后一个元素。空或缺失时为无操作。对应宏形式 {{popvar::name}} |
上述每个方法的 name 都接受点号路径(例如 roster.alice.hp),用于读写一个结构化变量内部的某片叶子。写入类方法直接就地修改 chat_metadata.variables[root],跟宏侧的 {{setvar::roster.alice.hp::value}} 行为一致;中间节点按需自动建立。
get / set 上的可选 args 参数支持:
args.key——备用变量名(覆盖name)args.index——存在变量里的 JSON 列表 / 字典的索引 / keyargs.as(仅 set)——为索引写入将值强制为'string'/'number'/'boolean'
全局变量
context.variables.global.get / set / add / inc / dec / del / has接口与本地相同。跨聊天持久化。
使用示例
const ctx = Luker.getContext();
// 读本地变量并给默认值
const turns = Number(ctx.variables.local.get('turns_taken')) || 0;
// 自增
ctx.variables.local.inc('turns_taken');
// 检查并初始化全局配置变量
if (!ctx.variables.global.has('api_endpoint')) {
ctx.variables.global.set('api_endpoint', 'https://api.example.com');
}
const endpoint = ctx.variables.global.get('api_endpoint');
// 索引写入:往本地变量里的 JSON 列表的某个位置写
ctx.variables.local.set('inventory', 'sword', { index: 0, as: 'string' });
ctx.variables.local.set('inventory', 'shield', { index: 1, as: 'string' });本地 vs 全局
| 本地 | 全局 | |
|---|---|---|
| 作用域 | 单个聊天 | 所有聊天 |
| 存储 | chat_metadata.variables | extension_settings.variables.global |
| 保存触发 | saveMetadataDebounced | saveSettingsDebounced |
| 适用场景 | 聊天专属计数器、阶段性状态 | 插件配置、跨聊天数据 |
楼层级写入
local / global 七件套之外,luker 在顶层还导出一个 setVariable,支持把单次写入挂到某一楼——这是 {{setvar::name::value}} 在文本里写出来效果的代码版等价物。
context.setVariable(
name: string,
value: any,
options?: { floor?: number },
): Promise<any>| 调用方式 | 效果 |
|---|---|
await ctx.setVariable(k, v) | 直接写 chat_metadata.variables[k] = v,跟 ctx.variables.local.set(k, v) 落到同一个桶,适合在异步代码里使用 |
await ctx.setVariable(k, v, { floor: N }) | 在第 N 楼的 extra.var_ops 末尾挂一条 setvar,绑在该楼的当前 swipe 上——后续 swipe 切换 / 删楼 / 建分支时,跟楼层一起被 variable-op-log 重放或回滚 |
带 floor 时的几点细节:
- 值会被强转字符串——
extra.var_ops的格式只承载字符串({{getvar}}取回的也是字符串)。需要存结构化对象请改用createFloorState(见 楼层级结构化 state)。 - 是重放,不是覆盖——swipe / 删楼 / 建分支时,rebuilder 会按当前活动 swipe 上所有 var_ops 的写入顺序,把它们触碰过的 key 在
chat_metadata.variables上重放一遍;没被任何 var_op 写过的 key(world-info 副作用、slash 命令、其他扩展写的值)原样保留。所以一次 floor 写入并不直接修改存储,而是为后续每次重放贡献一条指令。 floor必须是有效楼层索引(0 <= floor < chat.length),越界会抛错。- 点号路径名同样支持楼层级写入——
setVariable('roster.alice.hp', 50, { floor })会在第一个.处拆分,把path透传进 op 记录。op.key仍然是顶层变量名,重放时把整个结构当成一个单位。
const ctx = Luker.getContext();
// 立即写,跟 ctx.variables.local.set 落到同一个桶
await ctx.setVariable('quest_stage', 'intro');
// 绑定到最后一楼,跟着 swipe / 删楼 / 建分支一起重放
await ctx.setVariable('hp', 42, { floor: ctx.chat.length - 1 });floor 写入 vs createFloorState
| floor 写入 | createFloorState | |
|---|---|---|
| 数据形状 | 标量(字符串 / 数字) | 结构化对象 |
| 桶 | 跟 {{getvar::k}} 共用 chat_metadata.variables | 独立 namespace,不进 macro 系统 |
| 提交日志 | variable-op-log(楼层 extra.var_ops) | 楼层结构化提交日志(__floor_log) |
| 适合 | 跟 AI 写的 {{setvar}} 共享存储的可回滚标量 | CardApp / 插件自己管理的可回滚结构化状态 |
两个机制走的是各自独立的提交日志,同一个 key 不要两边都写——重建顺序无保证,容易互相覆盖。