fix(chat): 工具参数展开区不再截断,MCP 与长参数可完整核对 - #460
Conversation
展开工具卡片后无法核对实际执行了什么命令:动态 MCP 工具的长参数被砍到 60 字符只剩省略号,嵌套对象与数组则被整个丢弃;展示投影层还有一层 800 字符上限;内置 Bash 的折叠行按固定 48 字符硬切。 - 动态 mcp_* 工具的参数改走完整 JSON,配 whitespace-pre-wrap 换行与限高 滚动,长命令无需横向拖动即可通读 - 通用工具移除 60 字符硬截断:短原始值保留紧凑网格并完整显示,出现长文本 或嵌套结构时整体落入完整 JSON,展开态不再不可逆丢内容 - 通用网格改为遍历展示投影,__toolApprovalSummary 等合成参数不再泄漏 - 展示投影的 800 字符上限提到 20000 字符并改为深度遍历,可覆盖嵌套在对象 或数组中的超大字符串,截断时显式标注原始长度 - 折叠摘要行的视觉省略交给 CSS truncate 按实际窗口宽度决定,DOM 文本与新增 的悬浮 title 各留 600 字符防御上限 - 抽出 isDynamicMcpToolName 收敛原先三处 mcp_ 前缀硬编码 新增 10 条回归测试覆盖参数完整性、合成参数过滤、超大 payload 上限与折叠行 边界。agent-gui 前端 1656/1661 通过(5 项失败在未改动的干净主干上同样失败), gateway WebUI 563/563 通过,两端 tsc --noEmit 均通过。 Closes Stack-Cairn#444
|
PR governance checks passed. Awaiting human review. |
|
审核了当前 head 1. 合法的
|
问题
展开工具卡片后,「参数」区无法核对实际执行了什么命令。以 SSH / desktop-commander 这类会传入长命令的 MCP 工具为例,
cmdString被砍到 60 个字符就只剩省略号,后面几百上千字符没有任何查看入口。ToolResultDisplay.getToolDisplay通用分支slice(0, 60)+…string / number / booleanoptions: { ... }根本不显示uiMessages.toolCallArgsForDisplay默认分支ToolCallItem折叠摘要行firstLine.slice(0, 48)+…其中「嵌套结构被丢弃」这一条 issue 正文没有提到,但它决定了「修好」的验收标准——只把截断长度调大并不能让
options显示出来。另外补充一点:
5567eb62把前端逻辑抽到了crates/agent-ui之后,这段代码是桌面端与 WebUI 共用的,所以 issue 里「Affected area: Desktop UI」的范围偏窄,两端都受影响,本 PR 也一并修复。改动
1. 动态 MCP 工具改走完整 JSON
mcp_*前缀的动态工具参数形态不可预判(长命令、嵌套配置、数组),不适合「只显示原始类型」的紧凑网格。这类工具直接使用完整的展示投影 JSON,配whitespace-pre-wrap break-words换行与限高滚动,长命令不需要横向拖拽即可通读。2. 通用工具移除 60 字符硬截断,并按内容分流
这样既保住了绝大多数工具紧凑的视觉密度,又保证展开态不会不可逆地丢内容。
这里有一个必须说明的取舍:
ToolFactGrid本身没有高度约束也没有滚动容器,值是break-all换行的。如果只是简单地把 60 字符上限删掉,一个 20KB 的prompt参数会在气泡里铺成一面无限长的文字墙,并打乱虚拟列表的高度估算。所以长内容必须换到有限高滚动的ToolScrollablePre,而不是留在网格里。3. 通用网格改为遍历展示投影
原先直接遍历
toolCall.arguments,导致__toolApprovalPending、__toolApprovalSummary、__askUserQuestionDeadlineAt这些网关注入的合成参数会显示在网格里。这是既有问题,但移除 60 字符上限会让它更明显(__toolApprovalSummary是字符串,原本最多露 60 字符)。改为遍历toolCallArgsForDisplay(toolCall)后,合成参数按既有约定统一过滤掉。4. 展示投影:800 字符上限 → 20000 字符深度上限
直接删掉上限是不安全的:
safeStringify就是JSON.stringify(value, null, 2),没有任何 size cap。MCP 工具完全可能携带几 MB 的 payload(写文件、base64 图片),展开时会一次性序列化并塞进 DOM,还会被rawArgsDisplayCache这个WeakMap长期驻留。所以采用「宽松上限 + 显式标注」:
...(已截断,len=N),不允许静默丢内容WeakSet循环引用保护5. 折叠摘要行:固定字符数 → CSS 宽度自适应
truncate,按实际窗口宽度决定省略号位置(拖宽窗口能看到更多)title显示完整命令(原先 Bash 行没有 tooltip)title各留 600 字符防御上限——这一行在折叠状态下是常驻 DOM 的,不在LazyCollapse内,完全不设限会让超长单行命令(内联脚本、base64)在长对话里累积成真实的内存负担6. 抽出
isDynamicMcpToolNamemcp_前缀判断原先在assistantBubbleUtils、agentRunner、以及本次新增处共三份硬编码,收敛为单一真源,避免后续漂移。分享页对mcp_*工具内容的整体脱敏逻辑不变。效果
上图是修复后展开
mcp_desktop-commander_start_process的参数区,可以看到:shell、timeout_ms等其余参数与闭合}都在…,也无(已截断)标记(payload 991 字符,远低于 20000 上限)修复前同一次调用,参数区是一个紧凑网格,
command一项被砍成& 'C:\Program Files\Git\bin\bash.exe' -lc "msg='LiveAgent…,后面 900 多字符全部不可见,也没有任何展开入口。测试
新增
crates/agent-gui/test/chat/tool-argument-display.test.mjs,10 条回归测试,覆盖:isDynamicMcpToolName的分类边界(含McpManager不应命中)__toolApprovalPending/__toolApprovalSummary/ 流式预览 meta key)过滤title中按 TDD 流程:10 条测试先在修改前全部失败,实现后全部通过。
验证结果:
agent-gui前端套件tsc --noEmit(agent-gui / webui)关于
agent-gui那 5 项失败:经git stash在未改动的干净主干上复跑,同样失败,与本 PR 无关(Windows CRLF 签出环境下的 byte-for-byte 比对等存量问题)。Closes #444