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:
dj-oyu 2026-02-22 03:37:25 +09:00
parent 74c6828f50
commit 53d9722433
2 changed files with 57 additions and 22 deletions

View file

@ -990,32 +990,55 @@ func buildArgsSnippet(toolName string, args map[string]interface{}, workspace st
// entries under this avoids line-wrapping that causes height jitter. // entries under this avoids line-wrapping that causes height jitter.
const maxEntryLineWidth = 36 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. // formatCompactEntry formats a finished tool log entry as a fixed single line.
// The result marker (✓/✗) is always shown at the end regardless of truncation. // 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 { func formatCompactEntry(entry toolLogEntry) string {
// result is e.g. "✓ 1.2s" or "✗ 3.0s" — always 6-8 chars
result := entry.Result result := entry.Result
if result == "" { if result == "" {
result = "\u23F3" // ⏳ result = "\u23F3" // ⏳
} }
prefix := entry.Name // File tools: strip duration, keep only marker (✓/✗/⏳)
if entry.ArgsSnip != "" { isFile := isFileToolEntry(entry.Name)
prefix += " " + entry.ArgsSnip 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 for ArgsSnip: total - name - " " - " " - result
budget := maxEntryLineWidth - 1 - utf8.RuneCountInString(result) nameLen := utf8.RuneCountInString(entry.Name)
if budget < 4 { resultLen := utf8.RuneCountInString(result)
budget = 4 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) // No room for args or args empty
if len(prefixRunes) > budget { return entry.Name + " " + result
prefix = string(prefixRunes[:budget-1]) + "\u2026" // …
}
return prefix + " " + result
} }
// Display layout constants. // Display layout constants.

View file

@ -1484,23 +1484,32 @@ func TestFormatCompactEntry(t *testing.T) {
entry toolLogEntry entry toolLogEntry
wantSub string // must be a substring wantSub string // must be a substring
wantMark string // result marker must appear wantMark string // result marker must appear
noTime bool // if true, duration should NOT appear
}{ }{
{ {
name: "short entry", name: "exec short entry",
entry: toolLogEntry{Name: "exec", ArgsSnip: "ls", Result: "✓ 1.0s"}, entry: toolLogEntry{Name: "[1] exec", ArgsSnip: "ls", Result: "✓ 1.0s"},
wantSub: "exec ls", wantSub: "exec ls",
wantMark: "✓", wantMark: "✓ 1.0s", // exec keeps duration
}, },
{ {
name: "long entry is truncated with marker preserved", name: "exec long entry truncated from end",
entry: toolLogEntry{Name: "exec", ArgsSnip: "pytest tests/integration/test_very_long_name_that_exceeds_width.py", Result: "✗ 3.0s"}, entry: toolLogEntry{Name: "[2] exec", ArgsSnip: "pytest tests/integration/test_very_long_name.py", Result: "✗ 3.0s"},
wantMark: "✗", wantMark: "✗",
}, },
{ {
name: "no args", name: "file tool omits duration, shows filename",
entry: toolLogEntry{Name: "list_dir", Result: "✓ 0.1s"}, entry: toolLogEntry{Name: "[3] edit_file", ArgsSnip: "projects/terra/src/deep/nested/backend.py", Result: "✓ 0.0s"},
wantSub: "list_dir", wantSub: "backend.py",
wantMark: "✓", 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 { for _, tt := range tests {
@ -1512,6 +1521,9 @@ func TestFormatCompactEntry(t *testing.T) {
if !strings.Contains(got, tt.wantMark) { if !strings.Contains(got, tt.wantMark) {
t.Errorf("result marker %q missing from: %q", tt.wantMark, got) 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 // Must not exceed maxEntryLineWidth
if runeLen := len([]rune(got)); runeLen > maxEntryLineWidth { if runeLen := len([]rune(got)); runeLen > maxEntryLineWidth {
t.Errorf("entry too wide: %d runes (max %d): %q", runeLen, maxEntryLineWidth, got) t.Errorf("entry too wide: %d runes (max %d): %q", runeLen, maxEntryLineWidth, got)