提交前查重 / Duplicate check
涉及区域 / Affected area
运行时记忆 / Runtime Memory
Issue 类型 / Issue type
Bug 报告 / Bug report
摘要 / Summary
运行时记忆(Runtime Memory)的 remove 与 replace 通过 entry.content.includes(oldText) 做子串匹配,并要求命中数恰为 1。当某个条目内容只有一个字符(例如 "X"),且该字符也出现在同一 target 的其他条目中时,任何候选 oldText 都会命中多个条目,导致该条目既无法删除也无法替换。
预期结果 / Expected outcome
期望:删除/替换能基于条目的唯一标识(如稳定 id、index,或"内容完全相等"的精确匹配)定位,而不依赖"子串必须唯一"。具体地,用户应能删除内容为 "X" 的运行时记忆条目;删除后该条目不再出现在每轮上下文(MEMORY.md 投影)中,且不报 Multiple ... contain 错误。
详情或复现步骤 / Details or reproduction steps
最小复现(target=memory,工具或 UI 任一即可):
- 确保存在一条内容恰好为 "X" 的运行时记忆条目。
- 在同一 target 中再加入另一条包含大写 X 的条目(例如含 EGO_LINUX_CHROME 的条目——LINUX 里的 X)。
- 调用 mnemon_runtime_memory(action=remove, target=memory, old_text="X"),或在 UI 上对 "X" 条目点击"移除"。
- 观察到:Error: Multiple memory entries contain "X"; use a unique substring.,条目未被删除。
注意:无需手动构造冲突,未设置 branches 的热记忆在 WSL2 环境下常含 EGO_LINUX_CHROME 这类含 X 的子串,很容易自然踩中。
环境信息 / Environment
- DSH:dsh (web) 运行于 WSL2(宿主 Windows 10,不支持 WSL2 mirrored)
- dsh-mnemon:0.5.5
- dsh-mnemon-source-runtime:0.5.4
- Mnemon:0.2.5
- Node.js / pnpm:node v24.19.0
- OS / 浏览器:WSL2 Ubuntu 22.04,browser 为 GitHub 网页(Edge/Chrome)
- Provider / 相关配置:provider = mnemon-native(默认);persistenceStrategy = manual(仅允许 mnemon-native)
Bug 证据 / Bug evidence
Host / CLI 路径的实际报错:
Error: Multiple memory entries contain "X"; use a unique substring.
冒烟测试 / Smoke test
- 测试环境:同上(node v24.19.0,dsh-mnemon 0.5.5,provider=mnemon-native)
- 执行命令或操作:直接调用 mnemon_runtime_memory,action=remove, target=memory, old_text="X"
- 实际结果:报 Multiple memory entries contain "X"; use a unique substring.,"X" 条目未被删除,运行时热记忆条目数量不变。
- 对照实验(控制组):
- 先 replace 把另一条含 X 的条目中 EGO_LINUX_CHROME 临时改写为 ego_linux_chrome(消除大写 X)→ 成功;
- 再 remove old_text="X" → 成功(Entry removed.);
- 再 replace old_text="ego_linux_chrome" 还原为 EGO_LINUX_CHROME → 成功,且该条目与操作前 sha256 逐字节一致。
- 结论:问题确由"X 同时是另一条目的子串"导致,而非该条目本身损坏或数据缺失。
引用代码 / Code references
仓库内源码位置(omdsh-dev/dsh-mnemon,main @ 820b0b0):
-
plugins/dsh-mnemon-source-runtime/src/controller.ts:248-253 —— 匹配与报错核心(prepareMutation):
248: const oldText = normalizeContent(request.oldText, 'oldText')
249: const matches = entries
250: .map((entry, index) => entry.target === request.target && entry.content.includes(oldText) ? index : -1)
251: .filter(index => index >= 0)
252: if (matches.length === 0) throw new Error(No ${request.target} entry contains ${JSON.stringify(oldText)}.)
253: if (matches.length > 1) throw new Error(Multiple ${request.target} entries contain ${JSON.stringify(oldText)}; use a unique substring.)
-
plugins/dsh-mnemon-source-runtime/src/client/pages.tsx:78-81 —— UI "移除"按钮:
78: const remove = async (entry: RuntimeMemoryEntry) => {
81: await mutate({ action: 'remove', target: entry.target, old_text: entry.content })
说明:安装后经打包的 node_modules 内对应 bundle 为 dsh-mnemon-source-runtime/lib/index.js:209-212 与 lib/client.js:294-301,与上述源码一一对应;issue 请以上述仓库内源码路径为准。
补丁草案 / Patch proposal
建议:为 mutation 增加"内容完全相等"的精确优先匹配;更彻底是给每条运行时记忆分配稳定 id,用请求中的 id 定位。最小改法(概念 diff,放于 plugins/dsh-mnemon-source-runtime/src/controller.ts 的 prepareMutation 中,约 249-253 行):
--- a/plugins/dsh-mnemon-source-runtime/src/controller.ts
+++ b/plugins/dsh-mnemon-source-runtime/src/controller.ts
@@ (prepareMutation matches 计算,约 249-253 行)
- const matches = entries
- .map((entry, index) => entry.target === request.target && entry.content.includes(oldText) ? index : -1)
- .filter(index => index >= 0)
+ // 优先精确匹配:内容完全等于 oldText 且唯一时优先命中,
+ // 解决"单字符条目且该字符也出现在其他条目"导致的死锁
+ const exact = entries
+ .map((entry, index) => entry.target === request.target && entry.content === oldText ? index : -1)
+ .filter(index => index >= 0)
+ const matches = exact.length === 1
+ ? exact
+ : entries
+ .map((entry, index) => entry.target === request.target && entry.content.includes(oldText) ? index : -1)
+ .filter(index => index >= 0)
说明:当且仅当恰好一条条目内容精确等于 oldText 时优先命中它,否则退回子串匹配(保持现有兼容行为)。更彻底的方向:为条目分配稳定 id,remove/replace 按 id 定位,并对内容碰撞提供更明确的判别。
### 补充信息 / Additional context
_No response_
提交前查重 / Duplicate check
涉及区域 / Affected area
运行时记忆 / Runtime Memory
Issue 类型 / Issue type
Bug 报告 / Bug report
摘要 / Summary
运行时记忆(Runtime Memory)的 remove 与 replace 通过 entry.content.includes(oldText) 做子串匹配,并要求命中数恰为 1。当某个条目内容只有一个字符(例如 "X"),且该字符也出现在同一 target 的其他条目中时,任何候选 oldText 都会命中多个条目,导致该条目既无法删除也无法替换。
预期结果 / Expected outcome
期望:删除/替换能基于条目的唯一标识(如稳定 id、index,或"内容完全相等"的精确匹配)定位,而不依赖"子串必须唯一"。具体地,用户应能删除内容为 "X" 的运行时记忆条目;删除后该条目不再出现在每轮上下文(MEMORY.md 投影)中,且不报 Multiple ... contain 错误。
详情或复现步骤 / Details or reproduction steps
最小复现(target=memory,工具或 UI 任一即可):
注意:无需手动构造冲突,未设置 branches 的热记忆在 WSL2 环境下常含 EGO_LINUX_CHROME 这类含 X 的子串,很容易自然踩中。
环境信息 / Environment
Bug 证据 / Bug evidence
Host / CLI 路径的实际报错:
Error: Multiple memory entries contain "X"; use a unique substring.
冒烟测试 / Smoke test
引用代码 / Code references
仓库内源码位置(omdsh-dev/dsh-mnemon,main @ 820b0b0):
plugins/dsh-mnemon-source-runtime/src/controller.ts:248-253 —— 匹配与报错核心(prepareMutation):
248: const oldText = normalizeContent(request.oldText, 'oldText')
249: const matches = entries
250: .map((entry, index) => entry.target === request.target && entry.content.includes(oldText) ? index : -1)
251: .filter(index => index >= 0)
252: if (matches.length === 0) throw new Error(
No ${request.target} entry contains ${JSON.stringify(oldText)}.)253: if (matches.length > 1) throw new Error(
Multiple ${request.target} entries contain ${JSON.stringify(oldText)}; use a unique substring.)plugins/dsh-mnemon-source-runtime/src/client/pages.tsx:78-81 —— UI "移除"按钮:
78: const remove = async (entry: RuntimeMemoryEntry) => {
81: await mutate({ action: 'remove', target: entry.target, old_text: entry.content })
说明:安装后经打包的 node_modules 内对应 bundle 为 dsh-mnemon-source-runtime/lib/index.js:209-212 与 lib/client.js:294-301,与上述源码一一对应;issue 请以上述仓库内源码路径为准。
补丁草案 / Patch proposal
建议:为 mutation 增加"内容完全相等"的精确优先匹配;更彻底是给每条运行时记忆分配稳定 id,用请求中的 id 定位。最小改法(概念 diff,放于 plugins/dsh-mnemon-source-runtime/src/controller.ts 的 prepareMutation 中,约 249-253 行):