Skip to content

fix(chat): 工具参数展开区不再截断,MCP 与长参数可完整核对 - #460

Merged
su-fen merged 2 commits into
Stack-Cairn:mainfrom
24baigei:fix/tool-args-truncation-444
Aug 13, 2026
Merged

fix(chat): 工具参数展开区不再截断,MCP 与长参数可完整核对#460
su-fen merged 2 commits into
Stack-Cairn:mainfrom
24baigei:fix/tool-args-truncation-444

Conversation

@24baigei

@24baigei 24baigei commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

问题

展开工具卡片后,「参数」区无法核对实际执行了什么命令。以 SSH / desktop-commander 这类会传入长命令的 MCP 工具为例,cmdString 被砍到 60 个字符就只剩省略号,后面几百上千字符没有任何查看入口。

位置 行为 影响
ToolResultDisplay.getToolDisplay 通用分支 字符串值 slice(0, 60) + issue 截图里的直接症状
同上,通用分支只收集 string / number / boolean 嵌套对象与数组被整个丢弃 比截断更严重:options: { ... } 根本不显示
uiMessages.toolCallArgsForDisplay 默认分支 字符串超 800 字符替换为摘要 兜底路径同样丢内容
ToolCallItem 折叠摘要行 firstLine.slice(0, 48) + 窗口再宽也只显示 48 字符,且 Bash 行没有悬浮提示

其中「嵌套结构被丢弃」这一条 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 字符硬截断,并按内容分流

  • 短原始值:保留紧凑网格,且完整显示,不再 60 字符截断
  • 出现长文本(>200 字符)或嵌套结构:整体落入完整 JSON 视图

这样既保住了绝大多数工具紧凑的视觉密度,又保证展开态不会不可逆地丢内容。

这里有一个必须说明的取舍: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 长期驻留。

所以采用「宽松上限 + 显式标注」:

  • 上限提到 20000 字符,远超实际核对需要(本 PR 截图里 991 字符的命令完整显示,无任何截断标记)
  • 改为深度遍历,覆盖嵌套在对象/数组中的超大字符串(只截顶层挡不住批量写文件这类参数)
  • 触发截断时标注 ...(已截断,len=N),不允许静默丢内容
  • WeakSet 循环引用保护

5. 折叠摘要行:固定字符数 → CSS 宽度自适应

  • 视觉省略交给 CSS truncate,按实际窗口宽度决定省略号位置(拖宽窗口能看到更多)
  • 新增悬浮 title 显示完整命令(原先 Bash 行没有 tooltip)
  • DOM 文本与 title 各留 600 字符防御上限——这一行在折叠状态下是常驻 DOM 的,不在 LazyCollapse 内,完全不设限会让超长单行命令(内联脚本、base64)在长对话里累积成真实的内存负担

6. 抽出 isDynamicMcpToolName

mcp_ 前缀判断原先在 assistantBubbleUtilsagentRunner、以及本次新增处共三份硬编码,收敛为单一真源,避免后续漂移。分享页对 mcp_* 工具内容的整体脱敏逻辑不变。

效果

image copy

上图是修复后展开 mcp_desktop-commander_start_process 的参数区,可以看到:

  • 完整格式化 JSON,长命令从 Segment-01 连续到 Segment-16 后正常收尾,shelltimeout_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)过滤
  • 超大字符串的深度截断与显式长度标记
  • MCP 参数渲染为换行滚动的完整 JSON,且不泄漏合成参数
  • 通用工具的长值 / 嵌套值落入完整 JSON,短值保留紧凑网格且不截断
  • 折叠行不再 48 字符硬切、超长单行有上限、多行命令的完整内容在 title

按 TDD 流程:10 条测试先在修改前全部失败,实现后全部通过。

验证结果:

检查项 结果
新增回归测试 10/10 通过(改动前 10/10 失败)
agent-gui 前端套件 1656/1661 通过
Gateway WebUI 套件 563/563 通过
tsc --noEmit(agent-gui / webui) 均通过
Biome(改动文件,仅规则) 0 error
桌面端 release 构建 成功,实机验证如上图

关于 agent-gui 那 5 项失败:经 git stash 在未改动的干净主干上复跑,同样失败,与本 PR 无关(Windows CRLF 签出环境下的 byte-for-byte 比对等存量问题)。

Closes #444

展开工具卡片后无法核对实际执行了什么命令:动态 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
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

PR governance checks passed. Awaiting human review.

@StackCairn
StackCairn marked this pull request as draft August 13, 2026 13:35
@24baigei
24baigei marked this pull request as ready for review August 13, 2026 13:38
@StackCairn
StackCairn marked this pull request as draft August 13, 2026 13:38
@24baigei
24baigei marked this pull request as ready for review August 13, 2026 13:43
@su-fen

su-fen commented Aug 13, 2026

Copy link
Copy Markdown
Member

审核了当前 head 71223f21e6ac58707f8524f9ea82ec663162a065,并在合并最新 main 后做了针对性验证。当前 CI 全绿、GitHub 为 MERGEABLE / CLEAN,但有两个参数展示边界需要先修复,因此暂不建议直接合入。

1. 合法的 __* MCP 参数会被静默删除

toolCallArgsForDisplay() 的 default 分支当前使用:

if (key.startsWith("__")) continue;

动态 MCP 工具的参数 schema 不受 LiveAgent 控制,__custom__typename 等都可能是工具声明的合法业务参数。现在它们会在完整 JSON 展示投影中被无提示丢掉,仍然无法核对实际执行的完整参数。

最小复现:

输入:
{
  "__custom": "must-remain",
  "normal": "visible"
}

实际展示投影:
{
  "normal": "visible"
}

建议不要过滤整个 __ 命名空间,而是精确过滤 LiveAgent 已知的内部字段,例如流式预览、审批状态/截止时间/摘要和 AskUserQuestion 截止时间等常量。请同时新增一个动态 MCP 工具携带合法 __custom 参数的回归测试。

2. 20,000 字符限制没有控制整个 JSON 的总量

当前 TOOL_ARG_DISPLAY_MAX_CHARS = 20_000 只限制单个字符串。大量低于单字段上限的字符串仍会被完整深拷贝、同步 JSON.stringify()、送入 DOM,并缓存在 rawArgsDisplayCache 中。

我构造了 300 个字段、每个字段 19,999 字符:

  • 序列化结果约 6,005,292 字符;
  • 没有任何截断标记;
  • 所有字段都绕过了单字段限制。

这意味着 PR 描述中的“防止几 MB payload 原样序列化并缓存”尚未真正成立。建议在深度投影阶段维护累计字符/节点预算,预算耗尽时加入明确的整体截断标记;不要等完整 JSON.stringify() 后再切字符串。请增加“大量中等长度字段/大数组累计超过总预算”的测试。

已验证

  • PR 新增测试:10/10
  • GUI 前端:1676/1676
  • Gateway WebUI:563/563
  • GUI / WebUI tsc --noEmit:通过
  • pnpm check:ui-boundaries:通过
  • 改动文件 Biome error 检查:通过
  • git diff --check:通过
  • 最新 main 合并模拟:无冲突

现有测试与 CI 都通过,但没有覆盖上述两个输入边界。修复并补齐测试后可以再复核。

@su-fen
su-fen merged commit ced0ad4 into Stack-Cairn:main Aug 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] MCP 工具调用参数过长时被截断,无法展开查看完整命令

2 participants