diff --git a/client/__tests__/AgentChat.test.ts b/client/__tests__/AgentChat.test.ts
new file mode 100644
index 0000000..9c0186c
--- /dev/null
+++ b/client/__tests__/AgentChat.test.ts
@@ -0,0 +1,34 @@
+import { describe, it, expect } from 'vitest';
+import { getToolMessageDetails } from '../src/pages/AgentChat';
+
+describe('getToolMessageDetails', () => {
+ it('treats legacy codex content-only tool messages as foldable', () => {
+ const details = getToolMessageDetails({
+ id: 'tool-1',
+ role: 'tool',
+ content: 'Command: pwd\nOutput: /tmp/project\n(exit: 0)',
+ timestamp: 1,
+ });
+
+ expect(details).not.toBeNull();
+ expect(details?.title).toBe('Command: pwd');
+ expect(details?.details).toContain('Output: /tmp/project');
+ });
+
+ it('prefers structured tool fields when present', () => {
+ const details = getToolMessageDetails({
+ id: 'tool-2',
+ role: 'tool',
+ content: 'Command: pwd',
+ toolName: 'command',
+ toolInput: 'pwd',
+ toolResult: '/tmp/project\n[exit code] 0',
+ timestamp: 1,
+ });
+
+ expect(details).not.toBeNull();
+ expect(details?.title).toBe('Command: pwd');
+ expect(details?.input).toBe('pwd');
+ expect(details?.output).toContain('[exit code] 0');
+ });
+});
diff --git a/client/src/pages/AgentChat.tsx b/client/src/pages/AgentChat.tsx
index 9543971..ed9b1d7 100644
--- a/client/src/pages/AgentChat.tsx
+++ b/client/src/pages/AgentChat.tsx
@@ -15,6 +15,65 @@ import {
type ReasoningEffortSelection,
} from '../lib/reasoningEffort';
+type ChatMessage = Agent['messages'][number];
+type ToolMessageDetails = {
+ title: string;
+ input?: string;
+ output?: string;
+ details?: string;
+};
+
+function normalizeToolField(value?: string): string | undefined {
+ const trimmed = value?.trim();
+ return trimmed ? trimmed : undefined;
+}
+
+export function getToolMessageDetails(msg: ChatMessage): ToolMessageDetails | null {
+ if (msg.role !== 'tool') return null;
+
+ const toolInput = normalizeToolField(msg.toolInput);
+ const toolResult = normalizeToolField(msg.toolResult);
+ const content = normalizeToolField(msg.content);
+ const lines = content?.split('\n') || [];
+ const firstLine = lines[0];
+ const remaining = lines.slice(1).join('\n').trim();
+ const genericToolNames = new Set(['tool', 'command', 'command_execution', 'tool_call', 'function_call']);
+ const normalizedToolName = normalizeToolField(msg.toolName);
+
+ let title = (normalizedToolName && !genericToolNames.has(normalizedToolName))
+ ? normalizedToolName
+ : (firstLine || normalizedToolName || 'Tool');
+ let details: string | undefined;
+
+ if (toolInput || toolResult) {
+ if (content) {
+ const normalizedTitle = title.trim();
+ const normalizedContent = content.trim();
+ if (normalizedContent !== normalizedTitle && normalizedContent !== `Using tool: ${normalizedTitle}`) {
+ details = normalizedContent;
+ }
+ }
+ } else if (content) {
+ if (firstLine?.startsWith('Command:')) {
+ title = firstLine;
+ details = remaining || content;
+ } else if (firstLine?.startsWith('Tool:') || firstLine?.startsWith('Using tool:')) {
+ title = firstLine;
+ details = remaining || content;
+ } else {
+ title = firstLine || title;
+ details = remaining || content;
+ }
+ }
+
+ return {
+ title,
+ input: toolInput,
+ output: toolResult,
+ details,
+ };
+}
+
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme') || 'dark';
const next = current === 'dark' ? 'light' : 'dark';
@@ -930,7 +989,8 @@ export function AgentChat() {
{id &&
{msg.toolInput}
+ {toolDetails.input}
{msg.toolResult}
+ {toolDetails.output}
+ {toolDetails.details}