Skip to content

[Bug] 注入边界花括号转义在 0.7.32 已丢失(v0.7.4 曾修复):hot memory 带进会话原文里的 {{.Server.Version}} 让整轮永久失败 #162

Closed

Description

@lengduan

环境

  • dsh-mneme 0.7.32(npm 安装,DSH profile web)
  • DSH 0.1.5-rc.2,Windows 11
  • 相关配置全默认:autoInject: true、hotMemoryEnabled: true

现象

某个会话从某一轮起,每条新消息都在 turn/start 后立刻失败:

malformed prompt variable reference "{{.Server.Version}}" in context "memory" (variable names match /^[a-z][a-z0-9_]*$/)

而且是粘性的:该会话此后每条消息都以同一错误失败,无法恢复。

同一会话的时间线(本地时间):

时刻 事件
14:31:04 turn 41 start,prompt 组装成功
14:31:08 模型产出回复,其 reasoning 里写了 docker version --format "{{.Server.Version}}",随后发起工具调用
14:32:05 工具执行后第二次组装 prompt → 抛错,turn 41 整轮作废
14:39:36 turn 42(用户发「再检查一下」)→ 18 ms 后同样抛错

根因

  1. DSH 核心 interpolate()(packages/core/system-prompt)会扫描 section/context 文本里所有 {{...}},变量名不匹配 /^[a-z][a-z0-9_]*$/ 即 throw —— 这是核心的设计行为,不是本插件能改的。
  2. memory context 由 mneme 注册(src/inject.js),内容 = hot memory(本会话最近 N 轮问答原文)+ 长期记忆条目。
  3. hot memory 的 response 取 content 里所有带 .text 的部分,因此 assistant 的 reasoning 文本也会被注入(extractRounds → textOf)。
  4. 三处注入出口(renderHotContext()、render(candidates)、renderUserSettings())都是原样透传,没有任何花括号转义。

于是:模型只要在思考里写下一句 docker --format "{{.Server.Version}}"(或任何 Go template / Vue / Handlebars / wiki 标记片段),下一轮组装 prompt 时这段文本就进了 memory context → 校验 throw。又因为 hot memory 每次从会话日志重建,那条消息会一直留在最近 N 轮窗口里,于是会话永久卡死。

关键:这个问题 v0.7.4 修过,0.7.32 的代码里却没有了

dsh-mneme/CHANGELOG.md(v0.7.4,issue #40)与 README 版本表都写着:

修复:注入边界 run-based 花括号转义(连续 2+ 个花括号之间插 \:{{a}}→{\{a\}\}),奇数连续括号(如 {{{a}}})也不残留字面 {{,interpolate() 不再扫描到;覆盖 memory 块 / 短期上下文 / 用户设置三处注入出口。新增配置 escapePromptVariables(默认开,关闭时原样透传)

新增 6 个回归测试(inject 5 + config 1,含 {{{a}}} 奇数括号回归),全套 782 通过

但在 0.7.32 上实测:

  • 包内 lib/inject.js 与 src/inject.js 哈希一致,三处注入出口都没有转义代码;
  • 全包 grep escapePromptVariables:0 命中 —— lib/config.js 里没有这个 schema 项,Web 设置面板也没有该项;
  • GitHub main 分支的 dsh-mneme/src/inject.js(即最新代码)同样没有转义,三处出口仍是原样返回;
  • GitHub 全仓代码搜索 escapePromptVariables:只命中 dsh-mneme/README.md 与 dsh-mneme/CHANGELOG.md,没有任何 .js 文件。

即:文档说修了,代码里没有。疑似该修复在某次重构/同步中丢失(类似 #65 那种 src/lib 脱节,但这次 lib 与 src 都没有)。

影响

建议

  1. 恢复 v0.7.4 的 run-based 转义,并确认 escapePromptVariables(默认开)真的回到 config.js schema、三处注入出口与 Web 设置面板;
  2. 回归测试补一条 hot memory 路径:构造一个 assistant 事件、其 content 含 {{.Server.Version}},断言注入文本里不残留字面 {{ —— v0.7.4 的 5 个 inject 测试显然没覆盖到 reasoning 这条来源;
  3. 顺带可议:hot memory 是否应只取 assistant 的 text 片段、排除 reasoning。reasoning 里出现模板语法/正则/日志原文的概率远高于正文。

最小复现

  1. 任意会话里让模型在思考或回复中出现 {{.Server.Version}}(例如让它执行 docker version --format "{{.Server.Version}}");
  2. 该轮工具执行后即失败;
  3. 此后该会话每条新消息都以同一错误失败。

需要的话我可以提供会话 jsonl 片段与注入文本,便于加回归测试。

Activity

  1. self-assigned this
    on Sep 13, 2026
  2. added
    bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)
    on Sep 13, 2026
  3. modusensus commented on Sep 13, 2026

    @modusensus
    Member

    已修复,随 v0.8.0 上线(npm 0.8.0 / Release,PR #165)。

    修复三层:

    1. 恢复 v0.7.4 的注入边界转义(escapePromptVars,v0.7.11 重构时被整块误删——与你的根因第 1 点完全一致,谢谢抓到这个历史回归):三处注入出口(memory 块 / 短期上下文 / 用户设置)重新包裹,{{a}} → {\{a\}\},奇数连括号也不残留字面 {{;
    2. hot memory 跳过 reasoning part(根因第 3 点):textOf 只取 type==="text" 与字符串,assistant 思考里的 docker --format "{{.Server.Version}}" 从源头不再进注入文本——比 v0.7.4 只做转义更彻底;
    3. 恢复 escapePromptVariables 配置(默认 true,关闭时原样透传)。

    你的时间线分析(turn 41 二次组装抛错 → turn 42 18ms 即失败)与「粘性卡死」的机制描述完全准确——hot memory 每轮从会话日志重建,触发文本一直留在窗口内。升级到 0.8.0 后该会话即可恢复(转义后那些文本安全通过 interpolate)。

    再次感谢这份报告的质量:环境、版本、时间线、根因链、连 v0.7.4 历史修复都对上了——正是我们的 Issue 报告要求 希望看到的范本。

  4. moved this from 📋 Backlog to ✅ Done in dsh-mneme 任务看板on Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions