## 环境 - DSH: `0.1.0-rc.7`(Web GUI,Windows) - 插件: `@modusensus/dsh-mneme` `0.7.2`(npm 最新 `0.7.3` 同样存在,代码未变) - 相关核心包: `@deepseek-ai/dsh-system-prompt` `0.1.0-rc.8` ## 问题描述 当注入的记忆(context `memory` / `user-settings`)内容中包含任何 `{{...}}` 形式的文本时,**整轮对话直接失败**,而不是正常降级。 实际报错(来自记忆库 `llm_audit_logs` / 运行日志): ``` 本轮运行失败 malformed prompt variable reference "{{hl|}}" in context "memory" (variable names match /^[a-z][a-z0-9_]*$/) ``` 触发场景:用户消息/记忆里包含 MediaWiki 模板语法(如灰机 wiki 的 `{{hl|}}`、`{{黑幕}}`),或中文占位符(如学习平台知识点里的 `{{关键词}}`、`{{挖空}}`)——这些都是合法业务文本,但一旦被 mneme 记忆捕获并注入系统上下文,整个回合就崩了。 ## 复现步骤 1. 安装并启用 mneme(默认配置,`autoInject: true`)。 2. 让模型记住一条含模板语法的内容,例如用户指示"你应该学习使用 `{{hl|}}` 这个模板"(或任何含 `{{...}}` 的文本),使其进入记忆库。 3. 在下一轮对话中触发系统上下文渲染(注入 "memory" context)。 4. 整轮运行失败,报 `malformed prompt variable reference ... in context "memory"`。 ## 根因分析 变量识别的校验不在 mneme 里,而在 DSH 核心 `dsh-system-prompt` 的 `interpolate()`(`lib/index.js`): ```js const VARIABLE_NAME = /^[a-z][a-z0-9_]*$/; const GROUP_AT = /^\{\{([^{}]*)\}\}/; // 第 118 行:任何 {{...}} 组都会先校验变量名,不合法直接 throw if (!VARIABLE_NAME.test(name)) throw new Error( `malformed prompt variable reference "{{${name}}}" in ${kind} "${input.name}" (variable names match ${String(VARIABLE_NAME)})` ); ``` 即:**任何**注入 context 中出现的 `{{...}}` 都会被当成 prompt 变量引用做严格校验(名字必须匹配 `/^[a-z][a-z0-9_]*$/`,且必须是已注册变量 `provider`/`model`/`cwd` 等)。`{{hl|}}` 因含 `|`、`{{挖空}}` 因含非 ASCII 字符,都会触发 throw。 而 mneme 的注入(`lib/inject.js`)通过 `ctx.systemPrompt.context({ name: "memory" | "user-settings", ... })` **原样**透传记忆/画像/规则文本,没有任何转义或降级处理。记忆内容属于不可控的用户/助手文本,天然可能包含 `{{...}}`,于是任意一条这样的记忆都会让后续所有回合失败。 影响面:不只是 `{{hl|}}`——任何含 `{{...}}` 的记忆(模板语法、中文标记、代码示例、正则文本等)都会触发;且错误是 **throw 而非降级**,`renderPrompt`/`renderContextSnapshot` 直接中断。 ## 期望行为 - **mneme 侧**:注入系统上下文的记忆/画像/规则文本应当始终按字面渲染,不被当作变量识别;含 `{{...}}` 的内容不应导致运行失败。 - **DSH 侧(上游)**:`dsh-system-prompt` 的 `interpolate()` 对"看起来不像合法变量名"的 `{{...}}`(无法通过 `VARIABLE_NAME`)应当按字面文本处理而非 throw,只有**合法变量名但未注册/无值**才报错——这样任何插件的动态文本都不会被模板语法误伤。 ## 建议修复 1. **mneme(推荐,已本地验证)**:在注入边界转义花括号——`{{` → `\{\{`,`}}` → `\}\}`。转义后 `\{\{`/`\}\}` 不含 `{{`/`}}` 子串,`interpolate` 不再扫描到,内容保持可读且幂等(不会二次转义)。可加配置开关(如 `escapePromptVariables`,默认开)。 2. **上游 dsh-system-prompt**:区分"非法变量名"与"合法但未知变量":`VARIABLE_NAME` 不匹配的 `{{...}}` 视为字面文本(不 throw),仅对匹配变量名但未注册/无值的情况报错。这样对所有插件一劳永逸。 ## 本地临时补丁(供参考) 已对 mneme `lib/inject.js`(及 `src/`、`.ignored/` 副本)打补丁:新增 `escapeVars()` 并应用于记忆块 `render()`、短期上下文 `renderHotContext()`、用户设置 `renderUserSettings()` 三处输出。验证:语法检查通过,使用 DSH 真实 `interpolate` 逻辑复测,`{{hl|}}`/`{{挖空}}`/`{{关键词}}` 均正常渲染不再抛错。注意:修改 `node_modules` 下的文件会被插件升级覆盖,建议在本仓库正式修复。
Activity
Anans-Amarre commentedon Aug 30, 2026
感谢非常详细的报告和根因分析!这个 bug 已确认,很有价值。
复现确认:我核实了
lib/inject.js,renderUserSettings()(用户画像/规则)和 memory 块的render()/renderHotContext()输出确实是原样透传到ctx.systemPrompt.context({ name: "memory" | "user-settings", text }),没有任何花括号转义。配合autoInject: true,任何含{{...}}的记忆/规则文本都会在 DSH 核心interpolate()里被当成 prompt 变量校验,一旦名字不匹配/^[a-z][a-z0-9_]*$/就 throw——不只是{{hl|}},中文标记、代码示例、正则文本都会中招,而且是整轮直接失败而非降级。修复方案(与你第 1 条建议一致,推荐):
{{→\{\{、}}→\}\}render()、短期上下文renderHotContext()、用户设置renderUserSettings()escapePromptVariables(默认开),极少数需要字面渲染模板的场景可关闭interpolate()不再扫描到,无需动上游关于上游
dsh-system-prompt的修复(建议 2):认同"非法变量名按字面处理、仅合法但未注册变量才报错"的思路,但这属于核心包改动、影响所有插件,需要谨慎设计(要避免静默吞掉真正配置错误的情形)。mneme 侧先做转义兜底,上游改进作为后续跟进项。这个 bug 优先级高(任意含
{{...}}的业务文本就能让整个回合崩溃),我会尽快安排修复并发布。感谢报告,尤其是那个本地补丁的验证思路,非常专业!fix(issue#40,#41): escape prompt-variable braces + overlay close over…
chore: release v0.7.4 (issue #40 prompt-brace escaping + issue #41 ov…
Anans-Amarre commentedon Aug 31, 2026
已在 v0.7.4 修复并发布 ✅:注入边界改为 run-based 花括号转义(连续 2+ 个花括号之间插
\\,{{a}}→{\\{a\\}}),奇数连续如{{{a}}}也不会残留字面{{——interpolate() 不再扫到非法变量名。覆盖 memory 块 / 短期上下文 / 用户设置三处注入出口,并新增配置escapePromptVariables(默认开,关闭时原样透传)。已加回归测试(含{{{a}}}奇数括号场景,全套 782 全绿)。请升级@modusensus/[email protected]验证,感谢你的详细根因分析和本地补丁思路!x12xx-CNp commentedon Sep 4, 2026
感谢!
fix(issue#162): 恢复注入边界花括号转义(v0.7.4 修复在 v0.7.11 误删)+ hot memory 跳过 rea…