diff --git a/claudetool/bash.go b/claudetool/bash.go index 8c9e673be01e467581beaea2d310671ba946762e..1b5ebc0b148fc78f9e6f22fcadd7cecadbbba42e 100644 --- a/claudetool/bash.go +++ b/claudetool/bash.go @@ -141,6 +141,11 @@ type bashInput struct { Background bool `json:"background,omitempty"` } +// BashDisplayData is the display data sent to the UI for bash tool results. +type BashDisplayData struct { + WorkingDir string `json:"workingDir"` +} + type BackgroundResult struct { PID int OutFile string @@ -203,13 +208,15 @@ func (b *BashTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut { timeout := req.timeout(b.Timeouts) + display := BashDisplayData{WorkingDir: wd} + // If Background is set to true, use executeBackgroundBash if req.Background { result, err := b.executeBackgroundBash(ctx, req, timeout) if err != nil { return llm.ErrorToolOut(err) } - return llm.ToolOut{LLMContent: llm.TextContent(result.XMLish())} + return llm.ToolOut{LLMContent: llm.TextContent(result.XMLish()), Display: display} } // For foreground commands, use executeBash @@ -217,7 +224,7 @@ func (b *BashTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut { if execErr != nil { return llm.ErrorToolOut(execErr) } - return llm.ToolOut{LLMContent: llm.TextContent(out)} + return llm.ToolOut{LLMContent: llm.TextContent(out), Display: display} } const maxBashOutputLength = 131072 diff --git a/claudetool/bash_test.go b/claudetool/bash_test.go index db91ee39abb3f082ffc42f38c50d85c8c338ec0d..d1c0dea6bdfe7d415ad85e304324653d562fd3ae 100644 --- a/claudetool/bash_test.go +++ b/claudetool/bash_test.go @@ -87,6 +87,15 @@ func TestBashTool(t *testing.T) { if len(result) == 0 || result[0].Text != expected { t.Errorf("Expected %q, got %q", expected, result[0].Text) } + + // Verify Display data contains working directory + display, ok := toolOut.Display.(BashDisplayData) + if !ok { + t.Fatalf("Expected Display to be BashDisplayData, got %T", toolOut.Display) + } + if display.WorkingDir != "/" { + t.Errorf("Expected WorkingDir to be '/', got %q", display.WorkingDir) + } }) // Test with arguments diff --git a/ui/src/components/BashTool.tsx b/ui/src/components/BashTool.tsx index 8d1b9e22acde6a09d5e065a73ea952887e2eff43..f94ebfa1bb056593203555f31470c7632a9f9df6 100644 --- a/ui/src/components/BashTool.tsx +++ b/ui/src/components/BashTool.tsx @@ -1,6 +1,11 @@ import React, { useState } from "react"; import { LLMContent } from "../types"; +// Display data from the bash tool backend +interface BashDisplayData { + workingDir: string; +} + interface BashToolProps { // For tool_use (pending state) toolInput?: unknown; @@ -10,11 +15,28 @@ interface BashToolProps { toolResult?: LLMContent[]; hasError?: boolean; executionTime?: string; + display?: unknown; } -function BashTool({ toolInput, isRunning, toolResult, hasError, executionTime }: BashToolProps) { +function BashTool({ + toolInput, + isRunning, + toolResult, + hasError, + executionTime, + display, +}: BashToolProps) { const [isExpanded, setIsExpanded] = useState(false); + // Extract working directory from display data + const displayData: BashDisplayData | null = + display && + typeof display === "object" && + "workingDir" in display && + typeof display.workingDir === "string" + ? (display as BashDisplayData) + : null; + // Extract command from toolInput const command = typeof toolInput === "object" && @@ -51,6 +73,11 @@ function BashTool({ toolInput, isRunning, toolResult, hasError, executionTime }:
{displayData.workingDir}
+ {command}
diff --git a/ui/src/styles.css b/ui/src/styles.css
index de4cd7ece8c22620dd1d548fabf5bd307f94b27c..e3fd300f8eee5f84450bdeb6f8d7abe5786a53f7 100644
--- a/ui/src/styles.css
+++ b/ui/src/styles.css
@@ -1004,6 +1004,19 @@ button {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ flex-shrink: 1;
+ min-width: 0;
+}
+
+.bash-tool-cwd {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ color: var(--text-tertiary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ flex-shrink: 0;
+ max-width: 30%;
}
.bash-tool-running {