wafer-core 0.1.45__py3-none-any.whl → 0.1.47__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wafer_core/lib/trace_compare/__init__.py +12 -0
- wafer_core/lib/trace_compare/graph_formatter.py +263 -0
- wafer_core/lib/trace_compare/graph_formatter_detailed.py +225 -0
- wafer_core/lib/trace_compare/graph_matcher.py +315 -0
- wafer_core/lib/trace_compare/graph_matcher_v2.py +332 -0
- wafer_core/rollouts/_pytui/app.py +8 -0
- wafer_core/rollouts/_pytui/viewport.py +186 -0
- wafer_core/rollouts/agents.py +12 -0
- wafer_core/rollouts/progress_app.py +434 -148
- wafer_core/rollouts/scoring.py +19 -2
- {wafer_core-0.1.45.dist-info → wafer_core-0.1.47.dist-info}/METADATA +1 -1
- {wafer_core-0.1.45.dist-info → wafer_core-0.1.47.dist-info}/RECORD +13 -8
- {wafer_core-0.1.45.dist-info → wafer_core-0.1.47.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Viewport component for pytui - Elm-style scrollable text view.
|
|
2
|
+
|
|
3
|
+
A frozen dataclass + pure functions approach matching pytui's architecture.
|
|
4
|
+
Based on patterns from charmbracelet/bubbles viewport.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
# In your Model
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Model:
|
|
10
|
+
viewport: Viewport = Viewport()
|
|
11
|
+
|
|
12
|
+
# In update()
|
|
13
|
+
case KeyPress(key="j"):
|
|
14
|
+
new_vp = viewport_scroll_down(model.viewport, 1, visible_height)
|
|
15
|
+
return replace(model, viewport=new_vp), Cmd.none()
|
|
16
|
+
|
|
17
|
+
# In view()
|
|
18
|
+
visible = viewport_visible_lines(model.viewport, visible_height)
|
|
19
|
+
for line in visible:
|
|
20
|
+
lines.append(line)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from dataclasses import dataclass, replace
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _clamp(value: int, min_val: int, max_val: int) -> int:
|
|
29
|
+
"""Clamp value to [min_val, max_val]."""
|
|
30
|
+
if min_val > max_val:
|
|
31
|
+
min_val, max_val = max_val, min_val
|
|
32
|
+
return max(min_val, min(value, max_val))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Viewport:
|
|
37
|
+
"""Immutable viewport state.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
lines: Content as tuple of strings (one per line)
|
|
41
|
+
y_offset: Vertical scroll position (0 = top)
|
|
42
|
+
auto_follow: If True, scroll to bottom when new content added
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
lines: tuple[str, ...] = ()
|
|
46
|
+
y_offset: int = 0
|
|
47
|
+
auto_follow: bool = True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── Query functions ──────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def viewport_max_offset(vp: Viewport, visible_height: int) -> int:
|
|
54
|
+
"""Maximum valid y_offset for this viewport."""
|
|
55
|
+
return max(0, len(vp.lines) - visible_height)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def viewport_at_top(vp: Viewport) -> bool:
|
|
59
|
+
"""Check if viewport is scrolled to top."""
|
|
60
|
+
return vp.y_offset <= 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def viewport_at_bottom(vp: Viewport, visible_height: int) -> bool:
|
|
64
|
+
"""Check if viewport is scrolled to bottom."""
|
|
65
|
+
return vp.y_offset >= viewport_max_offset(vp, visible_height)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def viewport_scroll_percent(vp: Viewport, visible_height: int) -> float:
|
|
69
|
+
"""Get scroll position as percentage (0.0 to 1.0)."""
|
|
70
|
+
max_off = viewport_max_offset(vp, visible_height)
|
|
71
|
+
if max_off == 0:
|
|
72
|
+
return 1.0
|
|
73
|
+
return vp.y_offset / max_off
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def viewport_visible_lines(vp: Viewport, visible_height: int) -> list[str]:
|
|
77
|
+
"""Get the lines currently visible in the viewport."""
|
|
78
|
+
start = _clamp(vp.y_offset, 0, len(vp.lines))
|
|
79
|
+
end = _clamp(start + visible_height, start, len(vp.lines))
|
|
80
|
+
return list(vp.lines[start:end])
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── Scroll operations ────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def viewport_scroll_down(vp: Viewport, n: int, visible_height: int) -> Viewport:
|
|
87
|
+
"""Scroll down by n lines. Returns unchanged viewport if already at bottom."""
|
|
88
|
+
if viewport_at_bottom(vp, visible_height) or n <= 0:
|
|
89
|
+
return vp
|
|
90
|
+
max_off = viewport_max_offset(vp, visible_height)
|
|
91
|
+
new_offset = _clamp(vp.y_offset + n, 0, max_off)
|
|
92
|
+
# Re-enable auto_follow if we hit bottom
|
|
93
|
+
auto_follow = new_offset >= max_off
|
|
94
|
+
return replace(vp, y_offset=new_offset, auto_follow=auto_follow)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def viewport_scroll_up(vp: Viewport, n: int) -> Viewport:
|
|
98
|
+
"""Scroll up by n lines. Returns unchanged viewport if already at top."""
|
|
99
|
+
if viewport_at_top(vp) or n <= 0:
|
|
100
|
+
return vp
|
|
101
|
+
new_offset = max(0, vp.y_offset - n)
|
|
102
|
+
# Disable auto_follow when scrolling up
|
|
103
|
+
return replace(vp, y_offset=new_offset, auto_follow=False)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def viewport_page_down(vp: Viewport, visible_height: int) -> Viewport:
|
|
107
|
+
"""Scroll down by one page."""
|
|
108
|
+
return viewport_scroll_down(vp, visible_height, visible_height)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def viewport_page_up(vp: Viewport, visible_height: int) -> Viewport:
|
|
112
|
+
"""Scroll up by one page."""
|
|
113
|
+
return viewport_scroll_up(vp, visible_height)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def viewport_half_page_down(vp: Viewport, visible_height: int) -> Viewport:
|
|
117
|
+
"""Scroll down by half a page (Ctrl+D style)."""
|
|
118
|
+
return viewport_scroll_down(vp, visible_height // 2, visible_height)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def viewport_half_page_up(vp: Viewport, visible_height: int) -> Viewport:
|
|
122
|
+
"""Scroll up by half a page (Ctrl+U style)."""
|
|
123
|
+
return viewport_scroll_up(vp, visible_height // 2)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def viewport_goto_top(vp: Viewport) -> Viewport:
|
|
127
|
+
"""Jump to top of content."""
|
|
128
|
+
if viewport_at_top(vp):
|
|
129
|
+
return vp
|
|
130
|
+
return replace(vp, y_offset=0, auto_follow=False)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def viewport_goto_bottom(vp: Viewport, visible_height: int) -> Viewport:
|
|
134
|
+
"""Jump to bottom of content, re-enable auto_follow."""
|
|
135
|
+
max_off = viewport_max_offset(vp, visible_height)
|
|
136
|
+
return replace(vp, y_offset=max_off, auto_follow=True)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ── Content operations ───────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def viewport_set_content(vp: Viewport, lines: tuple[str, ...], visible_height: int) -> Viewport:
|
|
143
|
+
"""Replace all content. Adjusts scroll if past new content end."""
|
|
144
|
+
new_vp = replace(vp, lines=lines)
|
|
145
|
+
max_off = viewport_max_offset(new_vp, visible_height)
|
|
146
|
+
|
|
147
|
+
# If scrolled past content, jump to bottom
|
|
148
|
+
if new_vp.y_offset > max_off:
|
|
149
|
+
return replace(new_vp, y_offset=max_off)
|
|
150
|
+
|
|
151
|
+
# If auto_follow enabled, stay at bottom
|
|
152
|
+
if new_vp.auto_follow:
|
|
153
|
+
return replace(new_vp, y_offset=max_off)
|
|
154
|
+
|
|
155
|
+
return new_vp
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def viewport_append_line(vp: Viewport, line: str, visible_height: int) -> Viewport:
|
|
159
|
+
"""Append a single line. Auto-scrolls if auto_follow enabled."""
|
|
160
|
+
new_lines = vp.lines + (line,)
|
|
161
|
+
new_vp = replace(vp, lines=new_lines)
|
|
162
|
+
|
|
163
|
+
if vp.auto_follow:
|
|
164
|
+
max_off = viewport_max_offset(new_vp, visible_height)
|
|
165
|
+
return replace(new_vp, y_offset=max_off)
|
|
166
|
+
|
|
167
|
+
return new_vp
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def viewport_append_lines(vp: Viewport, lines: tuple[str, ...], visible_height: int) -> Viewport:
|
|
171
|
+
"""Append multiple lines. Auto-scrolls if auto_follow enabled."""
|
|
172
|
+
if not lines:
|
|
173
|
+
return vp
|
|
174
|
+
new_lines = vp.lines + lines
|
|
175
|
+
new_vp = replace(vp, lines=new_lines)
|
|
176
|
+
|
|
177
|
+
if vp.auto_follow:
|
|
178
|
+
max_off = viewport_max_offset(new_vp, visible_height)
|
|
179
|
+
return replace(new_vp, y_offset=max_off)
|
|
180
|
+
|
|
181
|
+
return new_vp
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def viewport_clear(vp: Viewport) -> Viewport:
|
|
185
|
+
"""Clear all content and reset scroll."""
|
|
186
|
+
return Viewport(lines=(), y_offset=0, auto_follow=True)
|
wafer_core/rollouts/agents.py
CHANGED
|
@@ -844,6 +844,18 @@ async def process_pending_tools(
|
|
|
844
844
|
if "content" in tool_call.args:
|
|
845
845
|
result_summary["content"] = tool_call.args["content"]
|
|
846
846
|
|
|
847
|
+
# For edit, capture file path and diff stats
|
|
848
|
+
elif tool_call.name == "edit":
|
|
849
|
+
if "file_path" in tool_call.args:
|
|
850
|
+
result_summary["path"] = tool_call.args["file_path"]
|
|
851
|
+
# Compute +/- lines from old_string and new_string
|
|
852
|
+
old_str = tool_call.args.get("old_string", "")
|
|
853
|
+
new_str = tool_call.args.get("new_string", "")
|
|
854
|
+
old_lines = old_str.count("\n") + (1 if old_str else 0)
|
|
855
|
+
new_lines = new_str.count("\n") + (1 if new_str else 0)
|
|
856
|
+
result_summary["lines_removed"] = old_lines
|
|
857
|
+
result_summary["lines_added"] = new_lines
|
|
858
|
+
|
|
847
859
|
# Extract key metrics from details (e.g., compiled, correct for kernelbench)
|
|
848
860
|
if tool_result.details:
|
|
849
861
|
for k, v in tool_result.details.items():
|