qwen-code Web Shell @ 提及图标芯片(mention icon chips)机制解析与验证指南
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
本篇文章围绕 qwen-code 仓库中 .qwen/e2e-tests/webshell-mention-icon-chips.md 这份验证文档展开,系统讲解 Web Shell 输入框内@提及(mention)功能如何把插入的引用文本渲染为带内置图标的 inline chip 标签、如何通过composerTagIcons注册自定义图标,以及相关的安全回归约束与可复现的验证方法。读完本文,你将掌握内置/自定义提及 chip 的渲染链路、图标解析与 CSS 转义的安全边界,并能在本地直接运行文档所列的测试与构建命令进行验证。
一、验证文档要解决的问题
在 Web Shell 的 composer 输入框中输入@会唤起提及(at-mention)菜单,用于引用扩展(extension)、文件(file)、MCP 资源(MCP resources)以及 skill 等对象。选择某个候选项后,输入框里不应出现一段生硬的纯文本(例如@ext:...、@mcp:...或文件引用路径),而应当插入一个内联 composer 标签(inline composer tag):一个带有图标、标签、值与可删除按钮的视觉芯片。该验证文档围绕三个测试组展开:
- Built-in mention chips(内置提及芯片):接受扩展、文件或 MCP 的
@提及后,编辑器插入原始序列化文本,并附着内联 composer 标签;可见结果是一个带内置图标的内联 chip,而不是@ext:...、@mcp:...或文件引用纯文本。 - Custom mention chips(自定义提及芯片):注册一个
atProvider,其候选项提供composerTag.kind = 'table',并向WebShell传入composerTagIcons={{ table: '<icon-url>' }};接受该候选项后应插入 provider 提供的insertText,并使用注册的 table 图标渲染内联 chip。 - Regression coverage(回归覆盖):自定义图标查找必须忽略继承的对象属性;未注册自定义图标时内置图标仍能正常解析;图标 URL 在写入 CSS 自定义属性前必须经过转义。
最后一部分还记录了 2026-07-16 的一次内置 SVG data URI 回归(基线版本0.18.5-preview.0),用于验证四个内置图标(extension、file、MCP、skill)在内联 composer 标签、顶部 composer 标签和已提交用户消息标签三种表面上的掩码节点渲染是否正常,以及任意 SVG data URI 和javascript:自定义图标 URL 是否仍被排除在 DOM 之外。
二、从「纯文本」到「图标 chip」:内联标签渲染链路
2.1@提及解析与菜单状态机
用户键入@后,useAtMentionMenuHook 负责解析当前光标处的提及查询并管理菜单状态。其核心入口是parseAtMention,正则AT_PATTERN = /@((?:[\p{L}\p{N}_./:-]|\\.)*)$/u负责匹配光标前最后一个@开始的片段,并校验前一个字符必须是空白或[\s([{'"]之一,避免误触(例如邮箱地址中的@`)。详见 useAtMentionMenu.ts。
菜单状态机包含categories与items两级:
categories层展示所有可用 provider(内置的 files / extensions / mcp-resources,以及宿主注册的自定义 provider);items层展示当前 provider 的候选项,支持 150ms 防抖(SEARCH_DEBOUNCE_MS = 150)、AbortController 取消过期请求以及内置 provider 的结果缓存。
内置 provider 通过createFileProvider、createExtensionProvider、createMcpResourcesProvider构造,并可用WebShellBuiltinAtProvidersConfig控制启用/禁用(支持布尔值、ID 数组或{ enabled, include, exclude }三种形态),见 useAtMentionMenu.ts 与 customization.tsx。
2.2 候选项到 composer tag 的转换
每个@候选项的类型是WebShellAtItem,其中composerTag?: WebShellComposerTag字段携带了选中后要创建的标签数据。WebShellComposerTag的核心结构为(见 customization.tsx):
export interface WebShellComposerTag { id: string; label?: string; // chip 中带主题色的标签文本 value?: string; // chip 中的主文本(如文件路径) removable?: boolean; // 是否允许删除(默认可删除) kind?: WebShellComposerTagKind; // 用于图标解析:extension | file | mcp | skill | 自定义字符串 icon?: WebShellIconSource; metadata?: unknown; serialized?: string; // 提交时真正写回文本流的序列化内容 }其中WebShellComposerTagKind为'extension' | 'file' | 'mcp' | 'skill' | (string & {}),即内置四类之外还允许任意自定义字符串(见 customization.tsx),这正是文档中composerTag.kind = 'table'自定义场景的类型基础。
选中候选项后,useAtMentionMenu会调用createComposerTagForItem生成标签,并把原始插入文本(insertText)写入编辑器,同时通过createInlineTagEffect效果(StateEffect)通知 composer 在该文本对应的文档区间上挂载内联标签装饰。相关实现见 useComposerCore.ts 中的addInlineTagEffect/removeInlineTagEffect/clearInlineTagsEffect。
2.3 CodeMirror 装饰层与 chip 的 DOM 结构
Web Shell 的 composer 基于 CodeMirror 6 构建。内联 chip 是通过StateField+Decoration.replace实现的:inlineComposerTagField维护一个DecorationSet,addInlineTagEffect把ComposerTagWidget作为替换装饰挂到对应区间上,并同时注册为EditorView.atomicRanges,保证光标移动与编辑不会“穿过” chip 内部(见 useComposerCore.ts)。
ComposerTagWidget.toDOM负责把标签渲染成 chip DOM,关键渲染步骤包括:
- 解析图标:
const iconUrl = this.tag.iconUrl ?? getComposerTagIconUrl(this.tag.kind); - 安全校验:仅当
isBuiltinComposerTagIconUrl(iconUrl) || isSafeImageSrc(iconUrl)时才使用该 URL,否则不渲染图标(safeIconUrl为undefined); - 构造图标节点:一个 12×12 的
<span>,使用background: currentColor配合mask: var(--composer-tag-icon-url) center / contain no-repeat(含-webkit-mask)渲染,图标 URL 通过cssUrlValue(safeIconUrl)写入--composer-tag-icon-url自定义属性; - 附加默认内容(label + value,超出
max-width:32ch省略)、tooltip(可选)与删除按钮×(removable !== false时)。
上述逻辑见 useComposerCore.ts。也正是“mask 节点(icon mask node)”一词的来源:图标本质上是把 SVG 当作 CSS mask 画在 chip 内部的节点上。
2.4 提交时:inline chip 如何还原成文本
发送消息时,buildComposerPromptWithInlineTagPlacements会把文档中的内联标签区间替换回序列化文本(replaceInlineTagPlacements按区间排序后逐段重组文本),再与顶部标签一起拼装 prompt(见 useComposerCore.ts)。也就是说:chip 只是编辑时的视觉投影,提交时仍还原为serialized文本(默认回退顺序为serialized -> value -> label -> id,见 composerTag.ts)。同时createInputAnnotationsFromComposerTags会为每个标签生成DaemonInputAnnotation(类型为reference,携带id、kind、label、value、metadata、serialized、removable),供会话记录层在用户消息中继续渲染 chip(见 composerTag.ts)。
三、自定义图标:composerTagIcons与atProvider的配合
3.1 定制入口与类型定义
宿主(host)可以在WebShellCustomization中声明composerTagIcons?: WebShellComposerTagIconMap,类型为Readonly<Record<string, string>>,即「标签 kind → 图标 URL」的映射(见 customization.tsx 与 customization.tsx)。文档给出的用法是:
composerTagIcons={{ table: '<icon-url>' }}同时注册一个自定义atProvider,其候选项的composerTag.kind设为'table':
{ id: 'my-tables', label: 'Tables', search: async ({ query, signal }) => [ { id: 'table:orders', label: 'orders', insertText: '@table:orders ', composerTag: { id: 'table:orders', kind: 'table', value: 'orders' }, }, ], }接受该候选项后,composer 会插入insertText(即@table:orders),并把标签解析为 inline chip——此时kind === 'table'在内置图标表中不存在,于是回退到composerTagIcons['table']注册的图标。
3.2 图标解析顺序与安全边界
图标解析集中在 composerTag.ts:
getOwnIconUrl(iconUrls, kind)使用Object.prototype.hasOwnProperty.call(iconUrls, kind)严格检查自身属性,这正是文档回归项中「自定义图标查找忽略继承的对象属性」的实现保证——即使某个 kind 名称恰好与Object.prototype上的属性同名(如constructor、toString),也不会被错误命中;getComposerTagIconUrl(kind, customIconUrls)的解析顺序为:自定义图标映射优先,其次内置图标映射(builtinTagIconUrls内部包含 extension/file/mcp/skill 四个 SVG 资源);isBuiltinComposerTagIconUrl通过内置图标 URL 的 Set 集合做白名单校验,只有「内置图标」或「通过isSafeImageSrc校验的 URL」才会被写入 DOM 的 mask 节点。
3.3 图标 URL 的 CSS 转义
文档明确要求「icon URLs are escaped before being written into CSS custom properties」。由于图标 URL 最终会被写入 CSS 自定义属性--composer-tag-icon-url并作为url(...)使用,URL 中若含引号、反斜杠、换行等字符可能破坏 CSS 语法甚至注入样式。cssUrlVar.ts中的cssUrlValue负责转义:
export function cssUrlValue(url: string): string { const escaped = url.replace(/["\\\n\r\f]/g, (char) => { switch (char) { case '"': case '\\': return `\\${char}`; case '\n': return '\\A '; case '\r': return '\\D '; case '\f': return '\\C '; default: return ''; } }); return `url("${escaped}")`; }其测试用例(cssUrlVar.test.ts)验证了'https://x.test/a"\\\nb.svg'被转义为url("https://x.test/a\"\\\A b.svg"),以及cssUrlVar('--icon-url', '/icons/table.svg')生成{ '--icon-url': 'url("/icons/table.svg")' }。正是这一层转义,配合isBuiltinComposerTagIconUrl || isSafeImageSrc的双重校验,使文档中「任意 SVG data URI 和javascript:自定义图标 URL 不进入 DOM」的回归结论成立。
四、内置 SVG data URI 回归验证(2026-07-16)
4.1 基线问题
全局qwen --version基线为0.18.5-preview.0。修复前的 DOM 矩阵显示:虽然四个内置图标都能解析为data:image/svg+xmlURL,但在内联 composer 标签、顶部 composer 标签和已提交用户消息标签三种表面上,渲染出的icon mask 节点数量为零——即图标 URL 已解析但未实际渲染成可见图标。
4.2 验证结论
修复后的验证结果如下:
- 从
@菜单接受一个扩展项,仍能在编辑器中插入kind: 'extension'的内联标签; - 内联 composer 标签、顶部 composer 标签、已提交用户消息标签三种表面,均渲染出 extension、file、MCP、skill 四个图标 mask 节点;
- 任意 SVG data URI 与
javascript:自定义图标 URL 在三种表面上均不出现于 DOM; - 聚焦 WebShell 场景下 5 个文件共 128 个测试通过(包含扩展
@接受路径); - 按现有 lockfile 声明的依赖集安装后,全仓库构建通过,全仓库 typecheck 通过。
这条回归与第一节提到的两个修复效果截图相互印证:修复前内联 chip 上「Icon mask node: Missing」,修复后变为「Icon mask node: Detected ✓」。
五、本地验证方法:命令与预期结果
5.1 单元测试
在packages/web-shell下运行提及菜单、composer 核心、图标解析与 CSS 转义四组测试:
cd packages/web-shell && npx vitest run \ client/hooks/useComposerCore.test.ts \ client/hooks/useAtMentionMenu.test.tsx \ client/components/composerTagIcons.test.ts \ client/utils/cssUrlVar.test.ts预期结果:通过,涉及 4 个文件、80 个测试。需要说明的是,当前仓库源码中图标解析逻辑位于 utils/composerTag.ts,对应的测试文件为 composerTag.test.ts;验证文档中记录的composerTagIcons.test.ts是编写验证时的既有文件清单,useComposerCore.test.ts、useAtMentionMenu.test.tsx、cssUrlVar.test.ts均存在且可直接运行。
5.2 ESLint 静态检查
npx eslint \ packages/web-shell/client/customization.tsx \ packages/web-shell/client/components/composerTagIcons.ts \ packages/web-shell/client/components/composerTagIcons.test.ts \ packages/web-shell/client/components/ChatEditor.tsx \ packages/web-shell/client/hooks/useAtMentionMenu.ts \ packages/web-shell/client/hooks/useAtMentionMenu.test.tsx \ packages/web-shell/client/hooks/useComposerCore.ts \ packages/web-shell/client/hooks/useComposerCore.test.ts \ packages/web-shell/client/index.ts \ packages/web-shell/client/App.tsx \ packages/web-shell/client/utils/cssUrlVar.ts \ packages/web-shell/client/utils/cssUrlVar.test.ts预期结果:全部通过。
5.3 构建验证
npm run build --workspace=packages/web-shell预期结果:构建通过(存在已有的 Vite large chunk 警告,不影响结果)。验证文档同时注明:当前环境未进行人工浏览器截图,行为覆盖停留在 hook 与渲染辅助函数边界,包构建用于校验 web-shell bundle 本身。
5.4 聚焦与全量验证(回归场景)
针对 2026-07-16 回归,聚焦 WebShell 结果为5 个文件、128 个测试通过(包含扩展@接受路径);随后按现有 lockfile 声明的依赖集完成安装后,全仓库构建与全仓库 typecheck 均通过。整个验证链路与文档中「Built-in SVG data URI regression」一节完全对应,可作为该功能后续任何图标相关改动的回归基线。
六、关键实现文件索引
以下是本文引用的核心实现与测试文件,便于深入阅读:
- packages/web-shell/client/hooks/useAtMentionMenu.ts:
@提及菜单状态机、provider 注册与候选项加载; - packages/web-shell/client/hooks/useComposerCore.ts:CodeMirror 编辑器装配、内联标签装饰与 chip DOM 渲染、提交时文本还原;
- packages/web-shell/client/utils/composerTag.ts:标签序列化/反序列化、图标解析(自定义优先、内置兜底)、内置图标白名单;
- packages/web-shell/client/utils/composerTag.test.ts:标签解析与图标解析的单元测试;
- packages/web-shell/client/utils/cssUrlVar.ts:CSS
url(...)转义与自定义属性生成; - packages/web-shell/client/utils/cssUrlVar.test.ts:CSS 转义与
cssUrlVar的单元测试; - packages/web-shell/client/customization.tsx:
WebShellCustomization、WebShellComposerTag、WebShellComposerTagIconMap、WebShellAtProvider等公共类型定义; - .qwen/e2e-tests/webshell-mention-icon-chips.md:本文依据的验证文档本体。
七、小结
Web Shell 的@提及图标芯片功能,本质上是「序列化文本 + 编辑期视觉投影」这一设计思想在 CodeMirror 装饰层上的落地:选中候选项时写入原始文本并挂上替换装饰,编辑期呈现为带图标/标签/删除按钮的 chip,提交时再无损还原为文本,同时以 input annotation 形式把标签语义传给会话记录层。自定义图标通过composerTagIcons按 kind 注册、优先于内置图标解析,并受到「自身属性检查 + 内置白名单/安全图片源校验 + CSS 转义」三重防护。验证文档及其背后 80/128 个测试用例,为这一交互与安全边界提供了可复现的回归基线。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考