{entry.renderMode === 'diff' ? (
= React.memo(({
);
};
+ if (isTodoTool) {
+ if (state.status === 'error' && 'error' in state) {
+ return (
+
+
{t('chat.toolPart.error')}
+
+ {state.error}
+
+
+ );
+ }
+
+ const todoOutput = renderTodoOutput(todoContent, {
+ total: t('chat.todo.total'),
+ inProgress: t('chat.todo.inProgress'),
+ pending: t('chat.todo.pending'),
+ completed: t('chat.todo.completed'),
+ cancelled: t('chat.todo.cancelled'),
+ }, { unstyled: true });
+
+ return (
+
+ {renderScrollableBlock(
+ todoOutput ?? (
+
+ ),
+ { className: 'p-2', maxHeightClass: 'max-h-[46vh]' },
+ )}
+
+ );
+ }
+
return (
= ({ part, messageId, agentMenti
"[&_[data-component='markdown-code']]:bg-transparent",
"[&_[data-component='markdown-code']>*:first-child]:hidden",
"[&_[data-component='markdown-code']>div]:inline",
- "[&_[data-component='markdown-code']>div]:p-0",
- "[&_[data-component='markdown-code']_pre]:inline",
- "[&_[data-component='markdown-code']_code]:inline",
- ]
+ "[&_[data-component='markdown-code']>div]:p-0",
+ "[&_[data-component='markdown-code']_pre]:inline",
+ "[&_[data-component='markdown-code']_code]:inline",
+ "[&_[data-md-code-line-numbers]]:hidden",
+ ]
)}
disableLinkSafety
enableFileReferences={false}
diff --git a/packages/ui/src/components/chat/message/parts/toolRenderUtils.test.ts b/packages/ui/src/components/chat/message/parts/toolRenderUtils.test.ts
new file mode 100644
index 00000000..a9485c2c
--- /dev/null
+++ b/packages/ui/src/components/chat/message/parts/toolRenderUtils.test.ts
@@ -0,0 +1,30 @@
+import { describe, expect, test } from 'bun:test';
+
+import { isExpandableTool, isStaticTool } from './toolRenderUtils';
+
+describe('tool rendering classification', () => {
+ test('keeps navigation tools compact', () => {
+ expect(isStaticTool('read')).toBe(true);
+ expect(isStaticTool('skill')).toBe(true);
+ expect(isExpandableTool('read')).toBe(false);
+ expect(isExpandableTool('skill')).toBe(false);
+ });
+
+ test('expands built-in tools without direct navigation', () => {
+ expect(isExpandableTool('grep')).toBe(true);
+ expect(isExpandableTool('webfetch')).toBe(true);
+ expect(isExpandableTool('todowrite')).toBe(true);
+ expect(isExpandableTool('plan_exit')).toBe(true);
+ });
+
+ test('expands custom and MCP tools', () => {
+ expect(isExpandableTool('linear_list_issues')).toBe(true);
+ expect(isExpandableTool('my-plugin_publish')).toBe(true);
+ expect(isStaticTool('linear_list_issues')).toBe(false);
+ });
+
+ test('normalizes dotted and indexed tool names', () => {
+ expect(isStaticTool('runtime.read:2')).toBe(true);
+ expect(isExpandableTool('runtime.custom_tool:2')).toBe(true);
+ });
+});
diff --git a/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts b/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts
index f577ca9d..62fc0a20 100644
--- a/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts
+++ b/packages/ui/src/components/chat/message/parts/toolRenderUtils.ts
@@ -1,9 +1,7 @@
-const EXPANDABLE_TOOL_NAMES = new Set([
- 'edit', 'multiedit', 'apply_patch', 'str_replace', 'str_replace_based_edit_tool',
- 'bash', 'shell', 'cmd', 'terminal',
- 'write', 'create', 'file_write',
- 'question', 'task', 'lsp',
-]);
+// Keep only tools with a direct in-app navigation destination compact. Every
+// other tool uses ToolPart so custom, plugin, and MCP calls expose their input
+// and output through the common expandable renderer.
+const STATIC_TOOL_NAMES = new Set(['read', 'skill']);
const STANDALONE_TOOL_NAMES = new Set(['task']);
@@ -21,7 +19,7 @@ const normalizeToolName = (toolName: unknown): string => {
};
export const isExpandableTool = (toolName: unknown): boolean => {
- return EXPANDABLE_TOOL_NAMES.has(normalizeToolName(toolName));
+ return !isStaticTool(toolName);
};
export const isStandaloneTool = (toolName: unknown): boolean => {
@@ -29,6 +27,5 @@ export const isStandaloneTool = (toolName: unknown): boolean => {
};
export const isStaticTool = (toolName: unknown): boolean => {
- if (typeof toolName !== 'string') return false;
- return !isExpandableTool(toolName) && !isStandaloneTool(toolName);
+ return STATIC_TOOL_NAMES.has(normalizeToolName(toolName));
};
diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts
index a0896c0b..7fca78d9 100644
--- a/packages/ui/src/lib/i18n/messages/en.ts
+++ b/packages/ui/src/lib/i18n/messages/en.ts
@@ -1986,7 +1986,10 @@ export const dict = {
'chat.toolPart.noOutputProduced': 'No output produced',
'chat.toolPart.output': 'Output',
'chat.toolPart.showRawJson': 'Show raw JSON',
- 'chat.toolPart.showFormattedJson': 'Show formatted JSON',
+ 'chat.toolPart.showFormattedJson': 'Show formatted JSON',
+ 'chat.toolPart.showNavigableJson': 'Show navigable JSON',
+ 'chat.toolPart.openFileAtFirstChange': 'Open file at first change',
+ 'chat.toolPart.openFileDiff': 'Open file diff',
'chat.toolPart.copyOutput': 'Copy output',
'chat.toolPart.copiedOutput': 'Copied output',
'chat.toolPart.copyOutputFailed': 'Failed to copy output',
diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts
index 55e5be06..f640e906 100644
--- a/packages/ui/src/lib/i18n/messages/es.ts
+++ b/packages/ui/src/lib/i18n/messages/es.ts
@@ -1953,6 +1953,9 @@ export const dict: Record = {
"chat.toolPart.output": "Salida",
"chat.toolPart.showRawJson": "Mostrar JSON sin formato",
"chat.toolPart.showFormattedJson": "Mostrar JSON formateado",
+ "chat.toolPart.showNavigableJson": "Mostrar JSON navegable",
+ "chat.toolPart.openFileAtFirstChange": "Abrir archivo en el primer cambio",
+ "chat.toolPart.openFileDiff": "Abrir diferencias del archivo",
"chat.toolPart.copyOutput": "Copiar salida",
"chat.toolPart.copiedOutput": "Salida copiada",
"chat.toolPart.copyOutputFailed": "No se pudo copiar la salida",
diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts
index 8af49a6f..47957084 100644
--- a/packages/ui/src/lib/i18n/messages/fr.ts
+++ b/packages/ui/src/lib/i18n/messages/fr.ts
@@ -2711,6 +2711,9 @@ export const dict = {
'chat.chatInput.reviewCommentsRemove': 'Retirer les commentaires de revue',
'chat.toolPart.showRawJson': 'Afficher le JSON brut',
'chat.toolPart.showFormattedJson': 'Afficher le JSON formaté',
+ 'chat.toolPart.showNavigableJson': 'Afficher le JSON navigable',
+ 'chat.toolPart.openFileAtFirstChange': 'Ouvrir le fichier à la première modification',
+ 'chat.toolPart.openFileDiff': 'Ouvrir les différences du fichier',
'chat.toolPart.copyOutput': 'Copier la sortie',
'chat.toolPart.copiedOutput': 'Sortie copiée',
'chat.toolPart.copyOutputFailed': 'Impossible de copier la sortie',
diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts
index 82b6c394..54c0890f 100644
--- a/packages/ui/src/lib/i18n/messages/ja.ts
+++ b/packages/ui/src/lib/i18n/messages/ja.ts
@@ -1986,6 +1986,9 @@ export const dict: Record = {
'chat.toolPart.output': '出力',
'chat.toolPart.showRawJson': '生JSONを表示',
'chat.toolPart.showFormattedJson': '整形JSONを表示',
+ 'chat.toolPart.showNavigableJson': 'ナビゲーション可能なJSONを表示',
+ 'chat.toolPart.openFileAtFirstChange': '最初の変更箇所でファイルを開く',
+ 'chat.toolPart.openFileDiff': 'ファイル差分を開く',
'chat.toolPart.copyOutput': '出力をコピー',
'chat.toolPart.copiedOutput': '出力をコピーしました',
'chat.toolPart.copyOutputFailed': '出力のコピーに失敗しました',
diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts
index 1f3ee29a..97095cae 100644
--- a/packages/ui/src/lib/i18n/messages/ko.ts
+++ b/packages/ui/src/lib/i18n/messages/ko.ts
@@ -1987,6 +1987,9 @@ export const dict: Record = {
'chat.toolPart.output': '출력',
'chat.toolPart.showRawJson': '원시 JSON 표시',
'chat.toolPart.showFormattedJson': '형식화된 JSON 표시',
+ 'chat.toolPart.showNavigableJson': '탐색 가능한 JSON 표시',
+ 'chat.toolPart.openFileAtFirstChange': '첫 번째 변경 위치에서 파일 열기',
+ 'chat.toolPart.openFileDiff': '파일 diff 열기',
'chat.toolPart.copyOutput': '출력 복사',
'chat.toolPart.copiedOutput': '출력 복사됨',
'chat.toolPart.copyOutputFailed': '출력을 복사하지 못했습니다',
diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts
index 5693f210..16ea1048 100644
--- a/packages/ui/src/lib/i18n/messages/pl.ts
+++ b/packages/ui/src/lib/i18n/messages/pl.ts
@@ -1306,6 +1306,9 @@ export const dict: Record = {
'chat.toolPart.output': 'Wyjście',
'chat.toolPart.showRawJson': 'Pokaż surowy JSON',
'chat.toolPart.showFormattedJson': 'Pokaż sformatowany JSON',
+ 'chat.toolPart.showNavigableJson': 'Pokaż nawigowalny JSON',
+ 'chat.toolPart.openFileAtFirstChange': 'Otwórz plik przy pierwszej zmianie',
+ 'chat.toolPart.openFileDiff': 'Otwórz różnice pliku',
'chat.toolPart.copyOutput': 'Kopiuj wyjście',
'chat.toolPart.copiedOutput': 'Skopiowano wyjście',
'chat.toolPart.copyOutputFailed': 'Nie udało się skopiować wyjścia',
diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts
index f30fac68..a0284f9f 100644
--- a/packages/ui/src/lib/i18n/messages/pt-BR.ts
+++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts
@@ -1953,6 +1953,9 @@ export const dict: Record = {
"chat.toolPart.output": "Saída",
"chat.toolPart.showRawJson": "Mostrar JSON bruto",
"chat.toolPart.showFormattedJson": "Mostrar JSON formatado",
+ "chat.toolPart.showNavigableJson": "Mostrar JSON navegável",
+ "chat.toolPart.openFileAtFirstChange": "Abrir arquivo na primeira alteração",
+ "chat.toolPart.openFileDiff": "Abrir diferenças do arquivo",
"chat.toolPart.copyOutput": "Copiar saída",
"chat.toolPart.copiedOutput": "Saída copiada",
"chat.toolPart.copyOutputFailed": "Falha ao copiar saída",
diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts
index 522eb0b2..26f6afb5 100644
--- a/packages/ui/src/lib/i18n/messages/uk.ts
+++ b/packages/ui/src/lib/i18n/messages/uk.ts
@@ -1953,6 +1953,9 @@ export const dict: Record = {
"chat.toolPart.output": "Вивід",
"chat.toolPart.showRawJson": "Показати сирий JSON",
"chat.toolPart.showFormattedJson": "Показати форматований JSON",
+ "chat.toolPart.showNavigableJson": "Показати навігаційний JSON",
+ "chat.toolPart.openFileAtFirstChange": "Відкрити файл на першій зміні",
+ "chat.toolPart.openFileDiff": "Відкрити diff файлу",
"chat.toolPart.copyOutput": "Скопіювати вивід",
"chat.toolPart.copiedOutput": "Вивід скопійовано",
"chat.toolPart.copyOutputFailed": "Не вдалося скопіювати вивід",
diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts
index 0381efa0..f5fbcb09 100644
--- a/packages/ui/src/lib/i18n/messages/zh-CN.ts
+++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts
@@ -1953,6 +1953,9 @@ export const dict: Record = {
'chat.toolPart.output': '输出',
'chat.toolPart.showRawJson': '显示原始 JSON',
'chat.toolPart.showFormattedJson': '显示格式化 JSON',
+ 'chat.toolPart.showNavigableJson': '显示可导航 JSON',
+ 'chat.toolPart.openFileAtFirstChange': '在首次更改处打开文件',
+ 'chat.toolPart.openFileDiff': '打开文件差异',
'chat.toolPart.copyOutput': '复制输出',
'chat.toolPart.copiedOutput': '已复制输出',
'chat.toolPart.copyOutputFailed': '复制输出失败',
diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts
index 909a673a..a7aaee4b 100644
--- a/packages/ui/src/lib/i18n/messages/zh-TW.ts
+++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts
@@ -1957,6 +1957,9 @@ export const dict: Record = {
'chat.toolPart.output': '輸出',
'chat.toolPart.showRawJson': '顯示原始 JSON',
'chat.toolPart.showFormattedJson': '顯示格式化 JSON',
+ 'chat.toolPart.showNavigableJson': '顯示可導覽 JSON',
+ 'chat.toolPart.openFileAtFirstChange': '在首次變更處開啟檔案',
+ 'chat.toolPart.openFileDiff': '開啟檔案差異',
'chat.toolPart.copyOutput': '複製輸出',
'chat.toolPart.copiedOutput': '已複製輸出',
'chat.toolPart.copyOutputFailed': '複製輸出失敗',
diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts
index 1983b7be..8bba2be3 100644
--- a/packages/ui/src/stores/useUIStore.ts
+++ b/packages/ui/src/stores/useUIStore.ts
@@ -575,7 +575,6 @@ interface UIStore {
sessionRecapEnabled: boolean;
sessionSuggestionEnabled: boolean;
collapsibleThinkingBlocks: boolean;
- groupReasoningBlocks: boolean;
chatRenderMode: ChatRenderMode;
activityRenderMode: ActivityRenderMode;
showDeletionDialog: boolean;
@@ -873,7 +872,6 @@ export const useUIStore = create()(
sessionRecapEnabled: true,
sessionSuggestionEnabled: true,
collapsibleThinkingBlocks: true,
- groupReasoningBlocks: true,
chatRenderMode: 'live',
activityRenderMode: 'summary',
showDeletionDialog: true,
diff --git a/packages/vscode/CHANGELOG.md b/packages/vscode/CHANGELOG.md
index e1647e69..cfe50a85 100644
--- a/packages/vscode/CHANGELOG.md
+++ b/packages/vscode/CHANGELOG.md
@@ -1,5 +1,8 @@
## [Unreleased]
+- Chat/Tools: every tool call now expands to show its input, result, and errors, including MCP, plugin, and custom tools; Read and Skill stay compact links to their files. JSON results now offer navigable summary, tree, and raw views.
+- Chat/Tools: expanded file-edit and patch results include per-file buttons to open the diff or jump to the first changed line in the editor.
+- Chat/Thinking: reasoning parts stay separate and in chronological order instead of merging into one block, and collapsed previews no longer show empty trailing HTML comments.
- Chat: Mermaid diagrams now have zoom controls (thanks to @c-w-xiaohei).
- Chat: code blocks can show line numbers that stay aligned while streaming, and a new Wrap Code Block Lines setting controls long-line wrapping.
- Chat: with Sticky User Header enabled, user messages no longer float over earlier messages in long conversations.