The reasoning trace was rendered twice: once as its own "thinking" row on inference.completed, and again as a ✼ block on the following tool row, whose Reasoning was copied from Model.lastReasoning. The fallback branch was worse — lastReasoning is current model state, so every tool row with no Reasoning of its own (all non-diff rows, all snapshot-restored rows) got the newest trace stamped on it retroactively. Drop the block, the copy, and the now-dead RouterEntry.Reasoning / Model.lastReasoning; the standalone row already covers tool turns. actionToolText clipped every summary to 48 columns, which cut tool output and — worse — harness coach text: read-before-write rejections, write blocks, gate feedback, exactly the messages that say whether the agent was steered or silently blocked. The action renderer already wraps to panel width, so the clip was the only single-line ceiling; raise it to 4000 chars. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -194,7 +194,6 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
}
|
||||
}
|
||||
case protocol.TypeInferenceStarted:
|
||||
m.lastReasoning = ""
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
s.Active = true
|
||||
s.addEvent(nowMillis(), "InferenceStarted", msg.StageID)
|
||||
@@ -214,7 +213,6 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
// row (revealed via the palette). Skipped when the model emits no separate channel.
|
||||
if strings.TrimSpace(msg.Reasoning) != "" {
|
||||
m.appendRouter(msg.SessionID, RouterEntry{Role: "thinking", Content: msg.Reasoning})
|
||||
m.lastReasoning = msg.Reasoning
|
||||
}
|
||||
}
|
||||
case protocol.TypeInferenceTimeout:
|
||||
@@ -255,7 +253,7 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
// existing collapsed diff row (^x opens the full diff).
|
||||
path, add, del := diffSummary(*msg.Diff)
|
||||
m.appendAction(msg.SessionID, "✎", "wrote "+path+countSuffix(add, del))
|
||||
m.appendRouter(msg.SessionID, RouterEntry{Role: "tool", Content: *msg.Diff, Reasoning: m.lastReasoning})
|
||||
m.appendRouter(msg.SessionID, RouterEntry{Role: "tool", Content: *msg.Diff})
|
||||
} else {
|
||||
label := msg.ToolName
|
||||
// Prefer the actual call args (path=…, command="…") over the affected-entities
|
||||
@@ -598,16 +596,21 @@ func paramSuffix(params []string) string {
|
||||
return " (" + clip(strings.Join(params, " · "), 200) + ")"
|
||||
}
|
||||
|
||||
// actionToolText joins a tool label with a short, clipped result summary.
|
||||
// actionToolText joins a tool label with its result summary. The action row wraps its
|
||||
// content to the panel width, so the summary is kept whole rather than clipped to one
|
||||
// line — tool output and harness coach text (read-before-write, write blocks, gate
|
||||
// feedback) are the payload, not decoration.
|
||||
func actionToolText(label, summary string) string {
|
||||
// Collapse to a single line first: tool summaries (dir listings, file heads)
|
||||
// often carry newlines, and a multi-line action row paints a background stripe
|
||||
// per line with the raw content leaking underneath.
|
||||
// Newlines are normalised away because the renderer re-wraps to panel width anyway;
|
||||
// leaving them in would paint a background stripe per raw line.
|
||||
s := strings.Join(strings.Fields(summary), " ")
|
||||
if s == "" {
|
||||
return label
|
||||
}
|
||||
return label + " · " + clip(s, 48)
|
||||
// ponytail: flat 4000-char ceiling so one recursive list_dir can't flood the
|
||||
// transcript. Swap for expand-on-select against the diff/preview surface if that
|
||||
// ceiling starts cutting real output.
|
||||
return label + " · " + clip(s, 4000)
|
||||
}
|
||||
|
||||
func approvalIcon(outcome string) string {
|
||||
|
||||
Reference in New Issue
Block a user