Skip to content

Commit a6080e2

Browse files
committed
Bound per-frame walk of read-family tool bodies to a tail window during live turns
1 parent 82f8891 commit a6080e2

1 file changed

Lines changed: 46 additions & 9 deletions

File tree

src/runtime/view/thread/turn/agent_timeline/tool_body_preview.cpp

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ void accumulate_grep_hits(const std::string& output, GrepHits& out) {
7979
// final card renders everything.
8080
constexpr std::size_t kStreamTailLines = 64;
8181

82+
// Live-tail window budget for line-oriented bodies (FileRead,
83+
// CodeBlock, GitDiff, Json, web_fetch). With show_all=false and
84+
// tail_only=true (maya's defaults for these kinds) the widget renders
85+
// only the last max(head,tail) lines — so feeding the full body makes
86+
// split_lines/elide walk O(file) every frame for a fixed-size preview.
87+
// A tail slice generous enough to cover every renderer's tail budget
88+
// keeps the visible output byte-identical while bounding per-frame cost
89+
// to O(window). The frozen snapshot (built under FrozenBuildScope) still
90+
// gets the full body — painted once, then blitted.
91+
constexpr std::size_t kLiveTailLines = 64;
92+
8293
[[nodiscard]] std::string tail_window(std::string_view s,
8394
std::size_t keep_lines) {
8495
if (s.empty()) return {};
@@ -95,6 +106,16 @@ constexpr std::size_t kStreamTailLines = 64;
95106
return std::string{s.substr(start)};
96107
}
97108

109+
// Body for a terminal line-oriented tool sitting in the LIVE tail.
110+
// Frozen builds keep the full body (full content, painted once); the
111+
// live path elides to a bounded tail window so per-frame split_lines
112+
// stays O(window) instead of O(file). Cheap no-op for short bodies
113+
// (tail_window returns the whole string when it has <= keep_lines).
114+
[[nodiscard]] std::string live_tail_body(std::string_view body) {
115+
if (building_frozen()) return std::string{body};
116+
return tail_window(body, kLiveTailLines);
117+
}
118+
98119
} // namespace
99120

100121
// ── Frozen-build scope ──────────────────────────────────────────────
@@ -171,9 +192,21 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
171192
auto b = body.find(kClose, a);
172193
if (b == std::string::npos) b = body.size();
173194
out.kind = Kind::GitDiff;
174-
out.text = body.substr(a, b - a);
195+
// Frozen build keeps the full diff (show_all, painted
196+
// once). In the live tail elide to a tail window so the
197+
// per-frame split_lines stays O(window) for a large edit
198+
// — same discipline as Write's terminal-but-not-frozen
199+
// branch.
200+
if (building_frozen()) {
201+
out.text = body.substr(a, b - a);
202+
out.show_all = true;
203+
} else {
204+
out.text = tail_window(
205+
std::string_view{body}.substr(a, b - a),
206+
kStreamTailLines);
207+
out.show_all = false;
208+
}
175209
out.text_color = text_tertiary;
176-
out.show_all = true;
177210
return out;
178211
}
179212
// Fence missing — fall through to args-based EditDiff below.
@@ -192,7 +225,11 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
192225
it != tc.args.end() && it->is_array() && !it->empty())
193226
{
194227
out.kind = Kind::EditDiff;
195-
out.show_all = !streaming_now;
228+
// show_all only in the frozen snapshot. While streaming
229+
// OR sitting in the live tail (re-rendered every frame)
230+
// keep the elided per-side/per-hunk preview so the cost
231+
// is bounded; the frozen card carries the full diff.
232+
out.show_all = !streaming_now && building_frozen();
196233
out.is_streaming = streaming_now;
197234
out.hunks.reserve(it->size());
198235
for (const auto& e : *it) {
@@ -209,7 +246,7 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
209246
if (nt.empty()) nt = safe_arg(tc.args, "new_string");
210247
if (!ot.empty() || !nt.empty()) {
211248
out.kind = Kind::EditDiff;
212-
out.show_all = !streaming_now;
249+
out.show_all = !streaming_now && building_frozen();
213250
out.is_streaming = streaming_now;
214251
out.hunks.push_back({std::move(ot), std::move(nt)});
215252
}
@@ -312,7 +349,7 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
312349
const auto& body = tc.output();
313350
if (!body.empty() && body != "no changes") {
314351
out.kind = Kind::GitDiff;
315-
out.text = body;
352+
out.text = live_tail_body(body);
316353
out.text_color = text_tertiary;
317354
}
318355
return out;
@@ -329,7 +366,7 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
329366
const auto& body = tc.output();
330367
if (!body.empty()) {
331368
out.kind = Kind::FileRead;
332-
out.text = body;
369+
out.text = live_tail_body(body);
333370
out.text_color = text_tertiary; // bright cyan — file content rendered as code
334371
// Anchor the gutter to the real source line numbers the tool
335372
// returned, not 1. The read tool accepts both `offset` and the
@@ -370,7 +407,7 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
370407
const auto& body = tc.output();
371408
if (!body.empty()) {
372409
out.kind = Kind::Json;
373-
out.text = body;
410+
out.text = live_tail_body(body);
374411
out.text_color = text_tertiary;
375412
}
376413
return out;
@@ -397,7 +434,7 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
397434
{
398435
if (!tc.output().empty()) {
399436
out.kind = Kind::CodeBlock;
400-
out.text = tc.output();
437+
out.text = live_tail_body(tc.output());
401438
out.text_color = text_tertiary;
402439
}
403440
return out;
@@ -407,7 +444,7 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
407444
// matches the card's failure cue; body content stays dim.
408445
if (tc.is_failed() && !tc.output().empty()) {
409446
out.kind = Kind::Failure;
410-
out.text = tc.output();
447+
out.text = live_tail_body(tc.output());
411448
out.chrome_color = status_error;
412449
return out;
413450
}

0 commit comments

Comments
 (0)