fix: compact file entries show filename and omit duration
File tool entries (edit_file, read_file, etc.) in compact mode now: - Omit duration (always near-instant, wastes space) - Truncate path from the start, keeping filename visible e.g. "…/backend.py ✓" instead of "projects/te… ✓ 0.0s" Exec entries keep full duration display and truncate from the end. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0b6ed5fcd1
commit
481819deb9
2 changed files with 57 additions and 22 deletions
|
|
@ -990,32 +990,55 @@ func buildArgsSnippet(toolName string, args map[string]interface{}, workspace st
|
|||
// entries under this avoids line-wrapping that causes height jitter.
|
||||
const maxEntryLineWidth = 36
|
||||
|
||||
// isFileToolEntry returns true if the entry name contains a file-operation tool.
|
||||
func isFileToolEntry(name string) bool {
|
||||
for _, t := range []string{"read_file", "write_file", "edit_file", "append_file", "list_dir"} {
|
||||
if strings.Contains(name, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// formatCompactEntry formats a finished tool log entry as a fixed single line.
|
||||
// The result marker (✓/✗) is always shown at the end regardless of truncation.
|
||||
// File tools omit duration (always near-instant); paths truncate from the
|
||||
// start so the filename is always visible.
|
||||
func formatCompactEntry(entry toolLogEntry) string {
|
||||
// result is e.g. "✓ 1.2s" or "✗ 3.0s" — always 6-8 chars
|
||||
result := entry.Result
|
||||
if result == "" {
|
||||
result = "\u23F3" // ⏳
|
||||
}
|
||||
|
||||
prefix := entry.Name
|
||||
if entry.ArgsSnip != "" {
|
||||
prefix += " " + entry.ArgsSnip
|
||||
// File tools: strip duration, keep only marker (✓/✗/⏳)
|
||||
isFile := isFileToolEntry(entry.Name)
|
||||
if isFile {
|
||||
if r := []rune(result); len(r) > 0 {
|
||||
result = string(r[0:1]) // just the symbol
|
||||
}
|
||||
}
|
||||
|
||||
// Budget: total ≤ maxEntryLineWidth, need space for " " + result
|
||||
budget := maxEntryLineWidth - 1 - utf8.RuneCountInString(result)
|
||||
if budget < 4 {
|
||||
budget = 4
|
||||
// Budget for ArgsSnip: total - name - " " - " " - result
|
||||
nameLen := utf8.RuneCountInString(entry.Name)
|
||||
resultLen := utf8.RuneCountInString(result)
|
||||
argsBudget := maxEntryLineWidth - nameLen - 1 - 1 - resultLen
|
||||
|
||||
args := entry.ArgsSnip
|
||||
if args != "" && argsBudget > 3 {
|
||||
argsRunes := []rune(args)
|
||||
if len(argsRunes) > argsBudget {
|
||||
// Paths: truncate from the start, keeping the filename visible
|
||||
if strings.Contains(args, "/") {
|
||||
args = "\u2026" + string(argsRunes[len(argsRunes)-argsBudget+1:])
|
||||
} else {
|
||||
args = string(argsRunes[:argsBudget-1]) + "\u2026"
|
||||
}
|
||||
}
|
||||
return entry.Name + " " + args + " " + result
|
||||
}
|
||||
|
||||
prefixRunes := []rune(prefix)
|
||||
if len(prefixRunes) > budget {
|
||||
prefix = string(prefixRunes[:budget-1]) + "\u2026" // …
|
||||
}
|
||||
|
||||
return prefix + " " + result
|
||||
// No room for args or args empty
|
||||
return entry.Name + " " + result
|
||||
}
|
||||
|
||||
// Display layout constants.
|
||||
|
|
|
|||
|
|
@ -1484,23 +1484,32 @@ func TestFormatCompactEntry(t *testing.T) {
|
|||
entry toolLogEntry
|
||||
wantSub string // must be a substring
|
||||
wantMark string // result marker must appear
|
||||
noTime bool // if true, duration should NOT appear
|
||||
}{
|
||||
{
|
||||
name: "short entry",
|
||||
entry: toolLogEntry{Name: "exec", ArgsSnip: "ls", Result: "✓ 1.0s"},
|
||||
name: "exec short entry",
|
||||
entry: toolLogEntry{Name: "[1] exec", ArgsSnip: "ls", Result: "✓ 1.0s"},
|
||||
wantSub: "exec ls",
|
||||
wantMark: "✓",
|
||||
wantMark: "✓ 1.0s", // exec keeps duration
|
||||
},
|
||||
{
|
||||
name: "long entry is truncated with marker preserved",
|
||||
entry: toolLogEntry{Name: "exec", ArgsSnip: "pytest tests/integration/test_very_long_name_that_exceeds_width.py", Result: "✗ 3.0s"},
|
||||
name: "exec long entry truncated from end",
|
||||
entry: toolLogEntry{Name: "[2] exec", ArgsSnip: "pytest tests/integration/test_very_long_name.py", Result: "✗ 3.0s"},
|
||||
wantMark: "✗",
|
||||
},
|
||||
{
|
||||
name: "no args",
|
||||
entry: toolLogEntry{Name: "list_dir", Result: "✓ 0.1s"},
|
||||
wantSub: "list_dir",
|
||||
name: "file tool omits duration, shows filename",
|
||||
entry: toolLogEntry{Name: "[3] edit_file", ArgsSnip: "projects/terra/src/deep/nested/backend.py", Result: "✓ 0.0s"},
|
||||
wantSub: "backend.py",
|
||||
wantMark: "✓",
|
||||
noTime: true,
|
||||
},
|
||||
{
|
||||
name: "file tool path truncates from start",
|
||||
entry: toolLogEntry{Name: "[4] read_file", ArgsSnip: "projects/terra-py-form/src/terra_py_form/hot/state/backend.py", Result: "✓ 0.1s"},
|
||||
wantSub: "backend.py",
|
||||
wantMark: "✓",
|
||||
noTime: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
@ -1512,6 +1521,9 @@ func TestFormatCompactEntry(t *testing.T) {
|
|||
if !strings.Contains(got, tt.wantMark) {
|
||||
t.Errorf("result marker %q missing from: %q", tt.wantMark, got)
|
||||
}
|
||||
if tt.noTime && strings.Contains(got, "0s") {
|
||||
t.Errorf("file tool should omit duration, got: %q", got)
|
||||
}
|
||||
// Must not exceed maxEntryLineWidth
|
||||
if runeLen := len([]rune(got)); runeLen > maxEntryLineWidth {
|
||||
t.Errorf("entry too wide: %d runes (max %d): %q", runeLen, maxEntryLineWidth, got)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue