1️⃣ 描述一下问题
Agent 对话输入框中,通过 @ 引用知识库/技能/MCP 等资源后,在其后输入一段文字。
将光标移动到所输入文字的第一个字前面(即引用 chip 与文字之间的空格之后),
按 Backspace:第一下删掉空格(符合预期),第二下会把整个输入框内容全部清空
(预期只删除引用 chip 本身)。
该问题在引用的资源名称不含空格时必现(此时 mention token 不加引号);
名称含空格/引号时 token 带引号,不触发。
2️⃣ 复现步骤
1. 打开任意 Agent 对话,在输入框输入 @ 并选择一个知识库(名称不含空格,如"示例知识库")
2. 继续输入一段文字,如"这个知识库有什么作用"
3. 按方向键 ← 将光标移动到"这"字前面(引用 chip 后空格之后)
4. 按 Backspace:删掉空格(正常)
5. 再按一次 Backspace:整个输入框内容被清空(异常)
预期行为:第二次 Backspace 仅删除引用 chip,保留后面的文字。
3️⃣ 原因分析(已定位)
底层文本中,mention token 与后文之间仅靠一个空格分隔。删掉空格后,文本变为:
@knowledge:示例知识库这个知识库有什么作用
`web/src/utils/mention_utils.js` 中 mentionTokenRegex 的非引用值分支为 `(\S+)`,
贪婪匹配会把后文全部吞进 mention value,此时整段文本被解析成一个 mention:
// Node 验证
"@knowledge:示例知识库这个知识库有什么作用".matchAll(
/@(file|knowledge|mcp|skill|subagent):(?:"((?:\\.|[^"\\])*)"|(\S+))/g
)
// => 匹配到 ["@knowledge:示例知识库这个知识库有什么作用"],覆盖整个字符串
而删除时 `expandMentionDeletionRange` 基于重新解析的字符串计算 mention 边界
(DOM 选区偏移仍基于旧 chip 的 dataset.mentionRaw,两者已不一致),
光标落在这个"大 mention"内,于是按"删除整个 mention"处理,清空全部内容。
标题:Error: 输入框删除 @ 引用前的分隔空格后,再按退格会清空整个输入内容
1️⃣ 描述一下问题 Agent 对话输入框中,通过 @ 引用知识库/技能/MCP 等资源后,在其后输入一段文字。 将光标移动到所输入文字的第一个字前面(即引用 chip 与文字之间的空格之后), 按 Backspace:第一下删掉空格(符合预期),第二下会把整个输入框内容全部清空 (预期只删除引用 chip 本身)。 该问题在引用的资源名称不含空格时必现(此时 mention token 不加引号); 名称含空格/引号时 token 带引号,不触发。 2️⃣ 复现步骤 1. 打开任意 Agent 对话,在输入框输入 @ 并选择一个知识库(名称不含空格,如"示例知识库") 2. 继续输入一段文字,如"这个知识库有什么作用" 3. 按方向键 ← 将光标移动到"这"字前面(引用 chip 后空格之后) 4. 按 Backspace:删掉空格(正常) 5. 再按一次 Backspace:整个输入框内容被清空(异常) 预期行为:第二次 Backspace 仅删除引用 chip,保留后面的文字。 3️⃣ 原因分析(已定位) 底层文本中,mention token 与后文之间仅靠一个空格分隔。删掉空格后,文本变为: @knowledge:示例知识库这个知识库有什么作用 `web/src/utils/mention_utils.js` 中 mentionTokenRegex 的非引用值分支为 `(\S+)`, 贪婪匹配会把后文全部吞进 mention value,此时整段文本被解析成一个 mention: // Node 验证 "@knowledge:示例知识库这个知识库有什么作用".matchAll( /@(file|knowledge|mcp|skill|subagent):(?:"((?:\\.|[^"\\])*)"|(\S+))/g ) // => 匹配到 ["@knowledge:示例知识库这个知识库有什么作用"],覆盖整个字符串 而删除时 `expandMentionDeletionRange` 基于重新解析的字符串计算 mention 边界 (DOM 选区偏移仍基于旧 chip 的 dataset.mentionRaw,两者已不一致), 光标落在这个"大 mention"内,于是按"删除整个 mention"处理,清空全部内容。