Skip to content

Commit 9031f99

Browse files
committed
feat(simulate): t shows and hides tool output, hidden by default
Tool outputs are payloads written for the model, not for a reader: a policy blob or a lookup table's worth of JSON between two spoken turns buries the conversation the job is about. Keep them off the transcript and put them behind t, whole rather than clipped, since a clipped payload answers nothing about why a job failed on a tool call. The tool call itself stays on the transcript either way — which tool ran, with a preview of its arguments, is part of reading what the agent did.
1 parent 5dce3ef commit 9031f99

1 file changed

Lines changed: 30 additions & 40 deletions

File tree

cmd/lk/simulate_tui.go

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ type simulateModel struct {
226226
detailPrinted string
227227
detailWidth int
228228
showLogs bool
229-
// tool call arguments and outputs are clipped to a preview in the
230-
// transcript; showToolDetail renders them whole instead
231-
showToolDetail bool
229+
// tool outputs are off the transcript unless asked for: they are payloads
230+
// written for the model, and at full length they bury the conversation
231+
showToolOutput bool
232232
logScrollOff int
233233
logPinned bool
234234
logPinnedTotal int
@@ -901,7 +901,7 @@ func (m *simulateModel) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
901901
m.logPinned = false
902902
case "t":
903903
if m.detailJobID != "" {
904-
m.showToolDetail = !m.showToolDetail
904+
m.showToolOutput = !m.showToolOutput
905905
}
906906
case "d":
907907
if m.detailJobID == "" && m.hasDescription() {
@@ -1875,15 +1875,18 @@ func (m *simulateModel) renderChatTranscript(jobID string) string {
18751875
case *agent.ChatContext_ChatItem_FunctionCall:
18761876
fc := v.FunctionCall
18771877
ensureAgentBlock()
1878-
m.writeToolItem(&b, fmt.Sprintf("ƒ %s(%s)", fc.Name, m.toolValue(fc.Arguments)), wrapWidth)
1878+
writeToolItem(&b, fmt.Sprintf("ƒ %s(%s)", fc.Name, clipToolValue(fc.Arguments)), wrapWidth)
18791879
case *agent.ChatContext_ChatItem_FunctionCallOutput:
1880+
if !m.showToolOutput {
1881+
continue
1882+
}
18801883
fco := v.FunctionCallOutput
18811884
output := strings.TrimSpace(fco.Output)
18821885
if output == "" {
18831886
continue
18841887
}
18851888
ensureAgentBlock()
1886-
m.writeToolItem(&b, "→ "+m.toolValue(output), wrapWidth)
1889+
writeToolItem(&b, "→ "+output, wrapWidth)
18871890
case *agent.ChatContext_ChatItem_AgentHandoff:
18881891
h := v.AgentHandoff
18891892
old := ""
@@ -1898,36 +1901,29 @@ func (m *simulateModel) renderChatTranscript(jobID string) string {
18981901
return b.String()
18991902
}
19001903

1901-
// toolPreviewLen is how much of a tool call's arguments or output the
1902-
// transcript shows when tool detail is collapsed — enough to tell two calls
1903-
// apart without a lookup table's worth of JSON burying the conversation.
1904+
// toolPreviewLen is how much of a tool call's arguments the transcript shows —
1905+
// enough to tell two calls apart without a lookup table's worth of JSON burying
1906+
// the conversation. Tool outputs are shown whole, or not at all.
19041907
const toolPreviewLen = 80
19051908

1906-
// toolValue is a tool argument blob or output as the transcript should carry
1907-
// it: clipped to a preview, or whole when tool detail is expanded.
1908-
func (m *simulateModel) toolValue(s string) string {
1909-
if m.showToolDetail || len(s) <= toolPreviewLen {
1909+
// clipToolValue is a tool argument blob as the transcript carries it.
1910+
func clipToolValue(s string) string {
1911+
if len(s) <= toolPreviewLen {
19101912
return s
19111913
}
1912-
// clip on a rune boundary; arguments and outputs carry guest names,
1913-
// currency symbols, and quoted speech
1914+
// clip on a rune boundary; arguments carry guest names, currency symbols,
1915+
// and quoted speech
19141916
clipped := s[:toolPreviewLen]
19151917
for len(clipped) > 0 && !utf8.ValidString(clipped) {
19161918
clipped = clipped[:len(clipped)-1]
19171919
}
19181920
return clipped + "..."
19191921
}
19201922

1921-
// writeToolItem appends one tool line to b. Collapsed, it is a single row and
1922-
// the terminal deals with any overflow. Expanded, it wraps to the transcript's
1923-
// measure with its continuations indented under the marker, so a long output
1924-
// stays readable as a block instead of one run-on row.
1925-
func (m *simulateModel) writeToolItem(b *strings.Builder, text string, wrapWidth int) {
1926-
if !m.showToolDetail {
1927-
b.WriteString(dimStyle.Render(" " + text))
1928-
b.WriteString("\n")
1929-
return
1930-
}
1923+
// writeToolItem appends one tool line to b, wrapped to the transcript's measure
1924+
// with its continuations indented under the marker, so a long output stays
1925+
// readable as a block instead of one run-on row.
1926+
func writeToolItem(b *strings.Builder, text string, wrapWidth int) {
19311927
for i, line := range wrapLines(text, wrapWidth-2) {
19321928
indent := " "
19331929
if i > 0 {
@@ -1938,10 +1934,9 @@ func (m *simulateModel) writeToolItem(b *strings.Builder, text string, wrapWidth
19381934
}
19391935
}
19401936

1941-
// hasToolDetail reports whether the open job's transcript holds anything the
1942-
// tool-detail toggle would reveal, so the hint is only offered when it does
1943-
// something.
1944-
func (m *simulateModel) hasToolDetail(jobID string) bool {
1937+
// hasToolOutput reports whether the open job's transcript holds a tool output,
1938+
// so the hint is only offered when the toggle would show something.
1939+
func (m *simulateModel) hasToolOutput(jobID string) bool {
19451940
if m.summary == nil || m.summary.ChatHistory == nil {
19461941
return false
19471942
}
@@ -1950,13 +1945,8 @@ func (m *simulateModel) hasToolDetail(jobID string) bool {
19501945
return false
19511946
}
19521947
for _, item := range chatCtx.Items {
1953-
switch v := item.Item.(type) {
1954-
case *agent.ChatContext_ChatItem_FunctionCall:
1955-
if len(v.FunctionCall.Arguments) > toolPreviewLen {
1956-
return true
1957-
}
1958-
case *agent.ChatContext_ChatItem_FunctionCallOutput:
1959-
if len(strings.TrimSpace(v.FunctionCallOutput.Output)) > toolPreviewLen {
1948+
if v, ok := item.Item.(*agent.ChatContext_ChatItem_FunctionCallOutput); ok {
1949+
if strings.TrimSpace(v.FunctionCallOutput.Output) != "" {
19601950
return true
19611951
}
19621952
}
@@ -2083,11 +2073,11 @@ func (m *simulateModel) renderHint() string {
20832073
case m.detailJobID != "":
20842074
// the job view is in the terminal's scrollback, which scrolls itself
20852075
parts = append(parts, "c copy scenario · ←/ESC back to list")
2086-
if m.hasToolDetail(m.detailJobID) {
2087-
if m.showToolDetail {
2088-
parts = append(parts, "t clip tool detail")
2076+
if m.hasToolOutput(m.detailJobID) {
2077+
if m.showToolOutput {
2078+
parts = append(parts, "t hide tool output")
20892079
} else {
2090-
parts = append(parts, "t full tool detail")
2080+
parts = append(parts, "t tool output")
20912081
}
20922082
}
20932083
if m.hasLogs() {

0 commit comments

Comments
 (0)