EvoScientist 0.0.1.dev3__py3-none-any.whl → 0.1.0rc1__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.
- EvoScientist/EvoScientist.py +17 -49
- EvoScientist/backends.py +0 -26
- EvoScientist/cli.py +1109 -255
- EvoScientist/middleware.py +8 -61
- EvoScientist/stream/__init__.py +0 -25
- EvoScientist/stream/utils.py +16 -23
- EvoScientist/tools.py +0 -64
- evoscientist-0.1.0rc1.dist-info/METADATA +199 -0
- evoscientist-0.1.0rc1.dist-info/RECORD +21 -0
- evoscientist-0.1.0rc1.dist-info/entry_points.txt +2 -0
- EvoScientist/memory.py +0 -715
- EvoScientist/paths.py +0 -45
- EvoScientist/skills/accelerate/SKILL.md +0 -332
- EvoScientist/skills/accelerate/references/custom-plugins.md +0 -453
- EvoScientist/skills/accelerate/references/megatron-integration.md +0 -489
- EvoScientist/skills/accelerate/references/performance.md +0 -525
- EvoScientist/skills/bitsandbytes/SKILL.md +0 -411
- EvoScientist/skills/bitsandbytes/references/memory-optimization.md +0 -521
- EvoScientist/skills/bitsandbytes/references/qlora-training.md +0 -521
- EvoScientist/skills/bitsandbytes/references/quantization-formats.md +0 -447
- EvoScientist/skills/find-skills/SKILL.md +0 -133
- EvoScientist/skills/find-skills/scripts/install_skill.py +0 -211
- EvoScientist/skills/flash-attention/SKILL.md +0 -367
- EvoScientist/skills/flash-attention/references/benchmarks.md +0 -215
- EvoScientist/skills/flash-attention/references/transformers-integration.md +0 -293
- EvoScientist/skills/llama-cpp/SKILL.md +0 -258
- EvoScientist/skills/llama-cpp/references/optimization.md +0 -89
- EvoScientist/skills/llama-cpp/references/quantization.md +0 -213
- EvoScientist/skills/llama-cpp/references/server.md +0 -125
- EvoScientist/skills/lm-evaluation-harness/SKILL.md +0 -490
- EvoScientist/skills/lm-evaluation-harness/references/api-evaluation.md +0 -490
- EvoScientist/skills/lm-evaluation-harness/references/benchmark-guide.md +0 -488
- EvoScientist/skills/lm-evaluation-harness/references/custom-tasks.md +0 -602
- EvoScientist/skills/lm-evaluation-harness/references/distributed-eval.md +0 -519
- EvoScientist/skills/ml-paper-writing/SKILL.md +0 -937
- EvoScientist/skills/ml-paper-writing/references/checklists.md +0 -361
- EvoScientist/skills/ml-paper-writing/references/citation-workflow.md +0 -562
- EvoScientist/skills/ml-paper-writing/references/reviewer-guidelines.md +0 -367
- EvoScientist/skills/ml-paper-writing/references/sources.md +0 -159
- EvoScientist/skills/ml-paper-writing/references/writing-guide.md +0 -476
- EvoScientist/skills/ml-paper-writing/templates/README.md +0 -251
- EvoScientist/skills/ml-paper-writing/templates/aaai2026/README.md +0 -534
- EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-supp.tex +0 -144
- EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026-unified-template.tex +0 -952
- EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bib +0 -111
- EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.bst +0 -1493
- EvoScientist/skills/ml-paper-writing/templates/aaai2026/aaai2026.sty +0 -315
- EvoScientist/skills/ml-paper-writing/templates/acl/README.md +0 -50
- EvoScientist/skills/ml-paper-writing/templates/acl/acl.sty +0 -312
- EvoScientist/skills/ml-paper-writing/templates/acl/acl_latex.tex +0 -377
- EvoScientist/skills/ml-paper-writing/templates/acl/acl_lualatex.tex +0 -101
- EvoScientist/skills/ml-paper-writing/templates/acl/acl_natbib.bst +0 -1940
- EvoScientist/skills/ml-paper-writing/templates/acl/anthology.bib.txt +0 -26
- EvoScientist/skills/ml-paper-writing/templates/acl/custom.bib +0 -70
- EvoScientist/skills/ml-paper-writing/templates/acl/formatting.md +0 -326
- EvoScientist/skills/ml-paper-writing/templates/colm2025/README.md +0 -3
- EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bib +0 -11
- EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.bst +0 -1440
- EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.pdf +0 -0
- EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.sty +0 -218
- EvoScientist/skills/ml-paper-writing/templates/colm2025/colm2025_conference.tex +0 -305
- EvoScientist/skills/ml-paper-writing/templates/colm2025/fancyhdr.sty +0 -485
- EvoScientist/skills/ml-paper-writing/templates/colm2025/math_commands.tex +0 -508
- EvoScientist/skills/ml-paper-writing/templates/colm2025/natbib.sty +0 -1246
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/fancyhdr.sty +0 -485
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bib +0 -24
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.bst +0 -1440
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.pdf +0 -0
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.sty +0 -246
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/iclr2026_conference.tex +0 -414
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/math_commands.tex +0 -508
- EvoScientist/skills/ml-paper-writing/templates/iclr2026/natbib.sty +0 -1246
- EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithm.sty +0 -79
- EvoScientist/skills/ml-paper-writing/templates/icml2026/algorithmic.sty +0 -201
- EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.bib +0 -75
- EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.pdf +0 -0
- EvoScientist/skills/ml-paper-writing/templates/icml2026/example_paper.tex +0 -662
- EvoScientist/skills/ml-paper-writing/templates/icml2026/fancyhdr.sty +0 -864
- EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.bst +0 -1443
- EvoScientist/skills/ml-paper-writing/templates/icml2026/icml2026.sty +0 -767
- EvoScientist/skills/ml-paper-writing/templates/icml2026/icml_numpapers.pdf +0 -0
- EvoScientist/skills/ml-paper-writing/templates/neurips2025/Makefile +0 -36
- EvoScientist/skills/ml-paper-writing/templates/neurips2025/extra_pkgs.tex +0 -53
- EvoScientist/skills/ml-paper-writing/templates/neurips2025/main.tex +0 -38
- EvoScientist/skills/ml-paper-writing/templates/neurips2025/neurips.sty +0 -382
- EvoScientist/skills/peft/SKILL.md +0 -431
- EvoScientist/skills/peft/references/advanced-usage.md +0 -514
- EvoScientist/skills/peft/references/troubleshooting.md +0 -480
- EvoScientist/skills/ray-data/SKILL.md +0 -326
- EvoScientist/skills/ray-data/references/integration.md +0 -82
- EvoScientist/skills/ray-data/references/transformations.md +0 -83
- EvoScientist/skills/skill-creator/LICENSE.txt +0 -202
- EvoScientist/skills/skill-creator/SKILL.md +0 -356
- EvoScientist/skills/skill-creator/references/output-patterns.md +0 -82
- EvoScientist/skills/skill-creator/references/workflows.md +0 -28
- EvoScientist/skills/skill-creator/scripts/init_skill.py +0 -303
- EvoScientist/skills/skill-creator/scripts/package_skill.py +0 -110
- EvoScientist/skills/skill-creator/scripts/quick_validate.py +0 -95
- EvoScientist/skills_manager.py +0 -392
- EvoScientist/stream/display.py +0 -604
- EvoScientist/stream/events.py +0 -415
- EvoScientist/stream/state.py +0 -343
- evoscientist-0.0.1.dev3.dist-info/METADATA +0 -321
- evoscientist-0.0.1.dev3.dist-info/RECORD +0 -113
- evoscientist-0.0.1.dev3.dist-info/entry_points.txt +0 -5
- {evoscientist-0.0.1.dev3.dist-info → evoscientist-0.1.0rc1.dist-info}/WHEEL +0 -0
- {evoscientist-0.0.1.dev3.dist-info → evoscientist-0.1.0rc1.dist-info}/licenses/LICENSE +0 -0
- {evoscientist-0.0.1.dev3.dist-info → evoscientist-0.1.0rc1.dist-info}/top_level.txt +0 -0
EvoScientist/stream/display.py
DELETED
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
"""Rich display functions for streaming CLI output.
|
|
2
|
-
|
|
3
|
-
Contains all rendering logic: tool call lines, sub-agent sections,
|
|
4
|
-
todo panels, streaming display layout, and final results display.
|
|
5
|
-
Also provides the shared console and formatter globals.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import os
|
|
10
|
-
import sys
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
from rich.console import Console, Group # type: ignore[import-untyped]
|
|
14
|
-
from rich.live import Live # type: ignore[import-untyped]
|
|
15
|
-
from rich.markdown import Markdown # type: ignore[import-untyped]
|
|
16
|
-
from rich.panel import Panel # type: ignore[import-untyped]
|
|
17
|
-
from rich.spinner import Spinner # type: ignore[import-untyped]
|
|
18
|
-
from rich.text import Text # type: ignore[import-untyped]
|
|
19
|
-
|
|
20
|
-
from .formatter import ToolResultFormatter
|
|
21
|
-
from .state import StreamState, SubAgentState, _build_todo_stats, _parse_todo_items
|
|
22
|
-
from .utils import DisplayLimits, ToolStatus, format_tool_compact, is_success
|
|
23
|
-
from .events import stream_agent_events
|
|
24
|
-
|
|
25
|
-
# ---------------------------------------------------------------------------
|
|
26
|
-
# Shared globals
|
|
27
|
-
# ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
console = Console(
|
|
30
|
-
legacy_windows=(sys.platform == 'win32'),
|
|
31
|
-
no_color=os.getenv('NO_COLOR') is not None,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
formatter = ToolResultFormatter()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# ---------------------------------------------------------------------------
|
|
38
|
-
# Todo formatting
|
|
39
|
-
# ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
def _format_single_todo(item: dict) -> Text:
|
|
42
|
-
"""Format a single todo item with status symbol."""
|
|
43
|
-
status = str(item.get("status", "todo")).lower()
|
|
44
|
-
content_text = str(item.get("content", item.get("task", item.get("title", ""))))
|
|
45
|
-
|
|
46
|
-
if status in ("done", "completed", "complete"):
|
|
47
|
-
symbol = "\u2713"
|
|
48
|
-
label = "done "
|
|
49
|
-
style = "green dim"
|
|
50
|
-
elif status in ("active", "in_progress", "in-progress", "working"):
|
|
51
|
-
symbol = "\u25cf"
|
|
52
|
-
label = "active"
|
|
53
|
-
style = "yellow"
|
|
54
|
-
else:
|
|
55
|
-
symbol = "\u25cb"
|
|
56
|
-
label = "todo "
|
|
57
|
-
style = "dim"
|
|
58
|
-
|
|
59
|
-
line = Text()
|
|
60
|
-
line.append(f" {symbol} ", style=style)
|
|
61
|
-
line.append(label, style=style)
|
|
62
|
-
line.append(" ", style="dim")
|
|
63
|
-
# Truncate long content
|
|
64
|
-
if len(content_text) > 60:
|
|
65
|
-
content_text = content_text[:57] + "\u2026"
|
|
66
|
-
line.append(content_text, style=style)
|
|
67
|
-
return line
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# ---------------------------------------------------------------------------
|
|
71
|
-
# Tool result formatting
|
|
72
|
-
# ---------------------------------------------------------------------------
|
|
73
|
-
|
|
74
|
-
def format_tool_result_compact(_name: str, content: str, max_lines: int = 5) -> list:
|
|
75
|
-
"""Format tool result as tree output.
|
|
76
|
-
|
|
77
|
-
Special handling for write_todos: shows formatted checklist with status symbols.
|
|
78
|
-
"""
|
|
79
|
-
elements = []
|
|
80
|
-
|
|
81
|
-
if not content.strip():
|
|
82
|
-
elements.append(Text(" \u2514 (empty)", style="dim"))
|
|
83
|
-
return elements
|
|
84
|
-
|
|
85
|
-
# Special handling for write_todos
|
|
86
|
-
if _name == "write_todos":
|
|
87
|
-
items = _parse_todo_items(content)
|
|
88
|
-
if items:
|
|
89
|
-
stats = _build_todo_stats(items)
|
|
90
|
-
stats_line = Text()
|
|
91
|
-
stats_line.append(" \u2514 ", style="dim")
|
|
92
|
-
stats_line.append(stats, style="dim")
|
|
93
|
-
elements.append(stats_line)
|
|
94
|
-
elements.append(Text("", style="dim")) # blank line
|
|
95
|
-
|
|
96
|
-
max_preview = 4
|
|
97
|
-
for item in items[:max_preview]:
|
|
98
|
-
elements.append(_format_single_todo(item))
|
|
99
|
-
|
|
100
|
-
remaining = len(items) - max_preview
|
|
101
|
-
if remaining > 0:
|
|
102
|
-
elements.append(Text(f" ... {remaining} more", style="dim italic"))
|
|
103
|
-
|
|
104
|
-
return elements
|
|
105
|
-
|
|
106
|
-
lines = content.strip().split("\n")
|
|
107
|
-
total_lines = len(lines)
|
|
108
|
-
|
|
109
|
-
display_lines = lines[:max_lines]
|
|
110
|
-
for i, line in enumerate(display_lines):
|
|
111
|
-
prefix = "\u2514" if i == 0 else " "
|
|
112
|
-
if len(line) > 80:
|
|
113
|
-
line = line[:77] + "\u2026"
|
|
114
|
-
style = "dim" if is_success(content) else "red dim"
|
|
115
|
-
elements.append(Text(f" {prefix} {line}", style=style))
|
|
116
|
-
|
|
117
|
-
remaining = total_lines - max_lines
|
|
118
|
-
if remaining > 0:
|
|
119
|
-
elements.append(Text(f" ... +{remaining} lines", style="dim italic"))
|
|
120
|
-
|
|
121
|
-
return elements
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# ---------------------------------------------------------------------------
|
|
125
|
-
# Tool call line rendering
|
|
126
|
-
# ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
def _render_tool_call_line(tc: dict, tr: dict | None) -> Text:
|
|
129
|
-
"""Render a single tool call line with status indicator."""
|
|
130
|
-
is_task = tc.get('name', '').lower() == 'task'
|
|
131
|
-
|
|
132
|
-
if tr is not None:
|
|
133
|
-
content = tr.get('content', '')
|
|
134
|
-
if is_success(content):
|
|
135
|
-
style = "bold green"
|
|
136
|
-
indicator = "\u2713" if is_task else ToolStatus.SUCCESS.value
|
|
137
|
-
else:
|
|
138
|
-
style = "bold red"
|
|
139
|
-
indicator = "\u2717" if is_task else ToolStatus.ERROR.value
|
|
140
|
-
else:
|
|
141
|
-
style = "bold yellow" if not is_task else "bold cyan"
|
|
142
|
-
indicator = "\u25b6" if is_task else ToolStatus.RUNNING.value
|
|
143
|
-
|
|
144
|
-
# Try to get display name from args first
|
|
145
|
-
tool_compact = format_tool_compact(tc['name'], tc.get('args'))
|
|
146
|
-
|
|
147
|
-
# If args were empty and we have a result, try to infer memory operations from result
|
|
148
|
-
tool_name = tc.get('name', '').lower()
|
|
149
|
-
if tool_name in ('write_file', 'edit_file') and tr is not None:
|
|
150
|
-
result_content = tr.get('content', '')
|
|
151
|
-
if '/MEMORY.md' in result_content or 'MEMORY.md' in result_content:
|
|
152
|
-
tool_compact = "Updating memory"
|
|
153
|
-
elif tool_name == 'read_file' and tr is not None:
|
|
154
|
-
result_content = tr.get('content', '')
|
|
155
|
-
# read_file result doesn't contain path, check if args is empty and result looks like memory
|
|
156
|
-
args = tc.get('args') or {}
|
|
157
|
-
if not args.get('path') and '# EvoScientist Memory' in result_content:
|
|
158
|
-
tool_compact = "Reading memory"
|
|
159
|
-
|
|
160
|
-
tool_text = Text()
|
|
161
|
-
tool_text.append(f"{indicator} ", style=style)
|
|
162
|
-
tool_text.append(tool_compact, style=style)
|
|
163
|
-
return tool_text
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# ---------------------------------------------------------------------------
|
|
167
|
-
# Sub-agent section rendering
|
|
168
|
-
# ---------------------------------------------------------------------------
|
|
169
|
-
|
|
170
|
-
def _render_subagent_section(sa: 'SubAgentState', compact: bool = False) -> list:
|
|
171
|
-
"""Render a sub-agent's activity as a bordered section.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
sa: Sub-agent state to render
|
|
175
|
-
compact: If True, render minimal 1-line summary (completed sub-agents)
|
|
176
|
-
|
|
177
|
-
Header uses "Cooking with {name}" style matching task tool format.
|
|
178
|
-
Active sub-agents show bordered tool list; completed ones collapse to 1 line.
|
|
179
|
-
"""
|
|
180
|
-
elements = []
|
|
181
|
-
BORDER = "dim cyan" if sa.is_active else "dim"
|
|
182
|
-
|
|
183
|
-
# Filter out tool calls with empty names
|
|
184
|
-
valid_calls = [tc for tc in sa.tool_calls if tc.get("name")]
|
|
185
|
-
|
|
186
|
-
# Split into completed and pending
|
|
187
|
-
completed = []
|
|
188
|
-
pending = []
|
|
189
|
-
for tc in valid_calls:
|
|
190
|
-
tr = sa.get_result_for(tc)
|
|
191
|
-
if tr is not None:
|
|
192
|
-
completed.append((tc, tr))
|
|
193
|
-
else:
|
|
194
|
-
pending.append(tc)
|
|
195
|
-
|
|
196
|
-
succeeded = sum(1 for _, tr in completed if tr.get("success", True))
|
|
197
|
-
failed = len(completed) - succeeded
|
|
198
|
-
|
|
199
|
-
# Build display name
|
|
200
|
-
display_name = f"Cooking with {sa.name}"
|
|
201
|
-
if sa.description:
|
|
202
|
-
desc = sa.description.split("\n")[0].strip()
|
|
203
|
-
desc = desc[:50] + "\u2026" if len(desc) > 50 else desc
|
|
204
|
-
display_name += f" \u2014 {desc}"
|
|
205
|
-
|
|
206
|
-
# --- Compact mode: 1-line summary for completed sub-agents ---
|
|
207
|
-
if compact:
|
|
208
|
-
line = Text()
|
|
209
|
-
if not sa.is_active:
|
|
210
|
-
line.append("\u2713 ", style="green")
|
|
211
|
-
line.append(display_name, style="green dim")
|
|
212
|
-
total = len(valid_calls)
|
|
213
|
-
line.append(f" ({total} tools)", style="dim")
|
|
214
|
-
else:
|
|
215
|
-
line.append("\u25b6 ", style="cyan")
|
|
216
|
-
line.append(display_name, style="bold cyan")
|
|
217
|
-
elements.append(line)
|
|
218
|
-
return elements
|
|
219
|
-
|
|
220
|
-
# --- Full mode: bordered section for Live streaming ---
|
|
221
|
-
|
|
222
|
-
# Header
|
|
223
|
-
header = Text()
|
|
224
|
-
header.append("\u250c ", style=BORDER)
|
|
225
|
-
if sa.is_active:
|
|
226
|
-
header.append(f"\u25b6 {display_name}", style="bold cyan")
|
|
227
|
-
else:
|
|
228
|
-
header.append(f"\u2713 {display_name}", style="bold green")
|
|
229
|
-
elements.append(header)
|
|
230
|
-
|
|
231
|
-
# Show every tool call with its status
|
|
232
|
-
for tc, tr in completed:
|
|
233
|
-
tc_line = Text("\u2502 ", style=BORDER)
|
|
234
|
-
tc_name = format_tool_compact(tc["name"], tc.get("args"))
|
|
235
|
-
if tr.get("success", True):
|
|
236
|
-
tc_line.append(f"\u2713 {tc_name}", style="green")
|
|
237
|
-
else:
|
|
238
|
-
tc_line.append(f"\u2717 {tc_name}", style="red")
|
|
239
|
-
content = tr.get("content", "")
|
|
240
|
-
first_line = content.strip().split("\n")[0][:70]
|
|
241
|
-
if first_line:
|
|
242
|
-
err_line = Text("\u2502 ", style=BORDER)
|
|
243
|
-
err_line.append(f"\u2514 {first_line}", style="red dim")
|
|
244
|
-
elements.append(tc_line)
|
|
245
|
-
elements.append(err_line)
|
|
246
|
-
continue
|
|
247
|
-
elements.append(tc_line)
|
|
248
|
-
|
|
249
|
-
# Pending/running tools
|
|
250
|
-
for tc in pending:
|
|
251
|
-
tc_line = Text("\u2502 ", style=BORDER)
|
|
252
|
-
tc_name = format_tool_compact(tc["name"], tc.get("args"))
|
|
253
|
-
tc_line.append(f"\u25cf {tc_name}", style="bold yellow")
|
|
254
|
-
elements.append(tc_line)
|
|
255
|
-
spinner_line = Text("\u2502 ", style=BORDER)
|
|
256
|
-
spinner_line.append("\u21bb running...", style="yellow dim")
|
|
257
|
-
elements.append(spinner_line)
|
|
258
|
-
|
|
259
|
-
# Footer
|
|
260
|
-
if not sa.is_active:
|
|
261
|
-
total = len(valid_calls)
|
|
262
|
-
footer = Text(f"\u2514 done ({total} tools)", style="dim green")
|
|
263
|
-
elements.append(footer)
|
|
264
|
-
elif valid_calls:
|
|
265
|
-
footer = Text("\u2514 running...", style="dim cyan")
|
|
266
|
-
elements.append(footer)
|
|
267
|
-
|
|
268
|
-
return elements
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
# ---------------------------------------------------------------------------
|
|
272
|
-
# Todo panel
|
|
273
|
-
# ---------------------------------------------------------------------------
|
|
274
|
-
|
|
275
|
-
def _render_todo_panel(todo_items: list[dict]) -> Panel:
|
|
276
|
-
"""Render a bordered Task List panel from todo items.
|
|
277
|
-
|
|
278
|
-
Matches the style: cyan border, status icons per item.
|
|
279
|
-
"""
|
|
280
|
-
lines = Text()
|
|
281
|
-
for i, item in enumerate(todo_items):
|
|
282
|
-
if i > 0:
|
|
283
|
-
lines.append("\n")
|
|
284
|
-
status = str(item.get("status", "todo")).lower()
|
|
285
|
-
content_text = str(item.get("content", item.get("task", item.get("title", ""))))
|
|
286
|
-
|
|
287
|
-
if status in ("done", "completed", "complete"):
|
|
288
|
-
symbol = "\u2713" # checkmark
|
|
289
|
-
style = "green dim"
|
|
290
|
-
elif status in ("active", "in_progress", "in-progress", "working"):
|
|
291
|
-
symbol = "\u23f3" # hourglass
|
|
292
|
-
style = "yellow"
|
|
293
|
-
else:
|
|
294
|
-
symbol = "\u25a1" # empty square
|
|
295
|
-
style = "dim"
|
|
296
|
-
|
|
297
|
-
lines.append(f"{symbol} ", style=style)
|
|
298
|
-
lines.append(content_text, style=style)
|
|
299
|
-
|
|
300
|
-
return Panel(
|
|
301
|
-
lines,
|
|
302
|
-
title="Task List",
|
|
303
|
-
title_align="center",
|
|
304
|
-
border_style="cyan",
|
|
305
|
-
padding=(0, 1),
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
# ---------------------------------------------------------------------------
|
|
310
|
-
# Streaming display layout
|
|
311
|
-
# ---------------------------------------------------------------------------
|
|
312
|
-
|
|
313
|
-
def create_streaming_display(
|
|
314
|
-
thinking_text: str = "",
|
|
315
|
-
response_text: str = "",
|
|
316
|
-
latest_text: str = "",
|
|
317
|
-
tool_calls: list | None = None,
|
|
318
|
-
tool_results: list | None = None,
|
|
319
|
-
is_thinking: bool = False,
|
|
320
|
-
is_responding: bool = False,
|
|
321
|
-
is_waiting: bool = False,
|
|
322
|
-
is_processing: bool = False,
|
|
323
|
-
show_thinking: bool = True,
|
|
324
|
-
subagents: list | None = None,
|
|
325
|
-
todo_items: list | None = None,
|
|
326
|
-
) -> Any:
|
|
327
|
-
"""Create Rich display layout for streaming output.
|
|
328
|
-
|
|
329
|
-
Returns:
|
|
330
|
-
Rich Group for Live display
|
|
331
|
-
"""
|
|
332
|
-
elements = []
|
|
333
|
-
tool_calls = tool_calls or []
|
|
334
|
-
tool_results = tool_results or []
|
|
335
|
-
subagents = subagents or []
|
|
336
|
-
|
|
337
|
-
# Initial waiting state
|
|
338
|
-
if is_waiting and not thinking_text and not response_text and not tool_calls:
|
|
339
|
-
spinner = Spinner("dots", text=" Thinking...", style="cyan")
|
|
340
|
-
elements.append(spinner)
|
|
341
|
-
return Group(*elements)
|
|
342
|
-
|
|
343
|
-
# Thinking panel
|
|
344
|
-
if show_thinking and thinking_text:
|
|
345
|
-
thinking_title = "Thinking"
|
|
346
|
-
if is_thinking:
|
|
347
|
-
thinking_title += " ..."
|
|
348
|
-
display_thinking = thinking_text
|
|
349
|
-
if len(display_thinking) > DisplayLimits.THINKING_STREAM:
|
|
350
|
-
display_thinking = "..." + display_thinking[-DisplayLimits.THINKING_STREAM:]
|
|
351
|
-
elements.append(Panel(
|
|
352
|
-
Text(display_thinking, style="dim"),
|
|
353
|
-
title=thinking_title,
|
|
354
|
-
border_style="blue",
|
|
355
|
-
padding=(0, 1),
|
|
356
|
-
))
|
|
357
|
-
|
|
358
|
-
# Tool calls and results paired display
|
|
359
|
-
# Collapse older completed tools to prevent overflow in Live mode
|
|
360
|
-
# Task tool calls are ALWAYS visible (they represent sub-agent delegations)
|
|
361
|
-
MAX_VISIBLE_TOOLS = 4
|
|
362
|
-
MAX_VISIBLE_RUNNING = 3
|
|
363
|
-
|
|
364
|
-
if tool_calls:
|
|
365
|
-
# Split into categories
|
|
366
|
-
completed_regular = [] # completed non-task tools
|
|
367
|
-
task_tools = [] # task tools (always visible)
|
|
368
|
-
running_regular = [] # running non-task tools
|
|
369
|
-
|
|
370
|
-
for i, tc in enumerate(tool_calls):
|
|
371
|
-
has_result = i < len(tool_results)
|
|
372
|
-
tr = tool_results[i] if has_result else None
|
|
373
|
-
is_task = tc.get('name') == 'task'
|
|
374
|
-
|
|
375
|
-
if is_task:
|
|
376
|
-
# Skip task calls with empty args (still streaming)
|
|
377
|
-
if tc.get('args'):
|
|
378
|
-
task_tools.append((tc, tr))
|
|
379
|
-
elif has_result:
|
|
380
|
-
completed_regular.append((tc, tr))
|
|
381
|
-
else:
|
|
382
|
-
running_regular.append((tc, None))
|
|
383
|
-
|
|
384
|
-
# --- Completed regular tools (collapsible) ---
|
|
385
|
-
slots = max(0, MAX_VISIBLE_TOOLS - len(running_regular))
|
|
386
|
-
hidden = completed_regular[:-slots] if slots and len(completed_regular) > slots else (completed_regular if not slots else [])
|
|
387
|
-
visible = completed_regular[-slots:] if slots else []
|
|
388
|
-
|
|
389
|
-
if hidden:
|
|
390
|
-
ok = sum(1 for _, tr in hidden if is_success(tr.get('content', '')))
|
|
391
|
-
fail = len(hidden) - ok
|
|
392
|
-
summary = Text()
|
|
393
|
-
summary.append(f"\u2713 {ok} completed", style="dim green")
|
|
394
|
-
if fail > 0:
|
|
395
|
-
summary.append(f" | {fail} failed", style="dim red")
|
|
396
|
-
elements.append(summary)
|
|
397
|
-
|
|
398
|
-
for tc, tr in visible:
|
|
399
|
-
elements.append(_render_tool_call_line(tc, tr))
|
|
400
|
-
content = tr.get('content', '') if tr else ''
|
|
401
|
-
if tr and not is_success(content):
|
|
402
|
-
result_elements = format_tool_result_compact(
|
|
403
|
-
tr['name'], content, max_lines=5,
|
|
404
|
-
)
|
|
405
|
-
elements.extend(result_elements)
|
|
406
|
-
|
|
407
|
-
# --- Running regular tools (limit visible) ---
|
|
408
|
-
hidden_running = len(running_regular) - MAX_VISIBLE_RUNNING
|
|
409
|
-
if hidden_running > 0:
|
|
410
|
-
summary = Text()
|
|
411
|
-
summary.append(f"\u25cf {hidden_running} more running...", style="dim yellow")
|
|
412
|
-
elements.append(summary)
|
|
413
|
-
running_regular = running_regular[-MAX_VISIBLE_RUNNING:]
|
|
414
|
-
|
|
415
|
-
for tc, tr in running_regular:
|
|
416
|
-
elements.append(_render_tool_call_line(tc, tr))
|
|
417
|
-
spinner = Spinner("dots", text=" Running...", style="yellow")
|
|
418
|
-
elements.append(spinner)
|
|
419
|
-
|
|
420
|
-
# Task tool calls are rendered as part of sub-agent sections below
|
|
421
|
-
|
|
422
|
-
# Response text handling
|
|
423
|
-
has_pending_tools = len(tool_calls) > len(tool_results)
|
|
424
|
-
any_active_subagent = any(sa.is_active for sa in subagents)
|
|
425
|
-
has_used_tools = len(tool_calls) > 0
|
|
426
|
-
all_done = not has_pending_tools and not any_active_subagent and not is_processing
|
|
427
|
-
|
|
428
|
-
# Intermediate narration (tools still running) -- dim italic above Task List
|
|
429
|
-
if latest_text and has_used_tools and not all_done:
|
|
430
|
-
preview = latest_text.strip()
|
|
431
|
-
if preview:
|
|
432
|
-
last_line = preview.split("\n")[-1].strip()
|
|
433
|
-
if last_line:
|
|
434
|
-
if len(last_line) > 60:
|
|
435
|
-
last_line = last_line[:57] + "\u2026"
|
|
436
|
-
elements.append(Text(f" {last_line}", style="dim italic"))
|
|
437
|
-
|
|
438
|
-
# Task List panel (persistent, updates on write_todos / read_todos)
|
|
439
|
-
todo_items = todo_items or []
|
|
440
|
-
if todo_items:
|
|
441
|
-
elements.append(Text("")) # blank separator
|
|
442
|
-
elements.append(_render_todo_panel(todo_items))
|
|
443
|
-
|
|
444
|
-
# Sub-agent activity sections
|
|
445
|
-
# Active: full bordered view; Completed: compact 1-line summary
|
|
446
|
-
for sa in subagents:
|
|
447
|
-
if sa.tool_calls or sa.is_active:
|
|
448
|
-
elements.extend(_render_subagent_section(sa, compact=not sa.is_active))
|
|
449
|
-
|
|
450
|
-
# Processing state after tool execution
|
|
451
|
-
if is_processing and not is_thinking and not is_responding and not response_text:
|
|
452
|
-
# Check if any sub-agent is active
|
|
453
|
-
any_active = any(sa.is_active for sa in subagents)
|
|
454
|
-
if not any_active:
|
|
455
|
-
spinner = Spinner("dots", text=" Analyzing results...", style="cyan")
|
|
456
|
-
elements.append(spinner)
|
|
457
|
-
|
|
458
|
-
# Final response -- render as Markdown when all work is done
|
|
459
|
-
if response_text and all_done:
|
|
460
|
-
elements.append(Text("")) # blank separator
|
|
461
|
-
elements.append(Markdown(response_text))
|
|
462
|
-
elif is_responding and not thinking_text and not has_pending_tools:
|
|
463
|
-
elements.append(Text("Generating response...", style="dim"))
|
|
464
|
-
|
|
465
|
-
return Group(*elements) if elements else Text("Processing...", style="dim")
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
# ---------------------------------------------------------------------------
|
|
469
|
-
# Final results display
|
|
470
|
-
# ---------------------------------------------------------------------------
|
|
471
|
-
|
|
472
|
-
def display_final_results(
|
|
473
|
-
state: StreamState,
|
|
474
|
-
thinking_max_length: int = DisplayLimits.THINKING_FINAL,
|
|
475
|
-
show_thinking: bool = True,
|
|
476
|
-
show_tools: bool = True,
|
|
477
|
-
) -> None:
|
|
478
|
-
"""Display final results after streaming completes."""
|
|
479
|
-
if show_thinking and state.thinking_text:
|
|
480
|
-
display_thinking = state.thinking_text
|
|
481
|
-
if len(display_thinking) > thinking_max_length:
|
|
482
|
-
half = thinking_max_length // 2
|
|
483
|
-
display_thinking = display_thinking[:half] + "\n\n... (truncated) ...\n\n" + display_thinking[-half:]
|
|
484
|
-
console.print(Panel(
|
|
485
|
-
Text(display_thinking, style="dim"),
|
|
486
|
-
title="Thinking",
|
|
487
|
-
border_style="blue",
|
|
488
|
-
))
|
|
489
|
-
|
|
490
|
-
if show_tools and state.tool_calls:
|
|
491
|
-
shown_sa_names: set[str] = set()
|
|
492
|
-
|
|
493
|
-
for i, tc in enumerate(state.tool_calls):
|
|
494
|
-
has_result = i < len(state.tool_results)
|
|
495
|
-
tr = state.tool_results[i] if has_result else None
|
|
496
|
-
content = tr.get('content', '') if tr is not None else ''
|
|
497
|
-
is_task = tc.get('name', '').lower() == 'task'
|
|
498
|
-
|
|
499
|
-
# Task tools: show delegation line + compact sub-agent summary
|
|
500
|
-
if is_task:
|
|
501
|
-
console.print(_render_tool_call_line(tc, tr))
|
|
502
|
-
sa_name = tc.get('args', {}).get('subagent_type', '')
|
|
503
|
-
task_desc = tc.get('args', {}).get('description', '')
|
|
504
|
-
matched_sa = None
|
|
505
|
-
for sa in state.subagents:
|
|
506
|
-
if sa.name == sa_name or (task_desc and task_desc in (sa.description or '')):
|
|
507
|
-
matched_sa = sa
|
|
508
|
-
break
|
|
509
|
-
if matched_sa:
|
|
510
|
-
shown_sa_names.add(matched_sa.name)
|
|
511
|
-
for elem in _render_subagent_section(matched_sa, compact=True):
|
|
512
|
-
console.print(elem)
|
|
513
|
-
continue
|
|
514
|
-
|
|
515
|
-
# Regular tools: show tool call line + result
|
|
516
|
-
console.print(_render_tool_call_line(tc, tr))
|
|
517
|
-
if has_result and tr is not None:
|
|
518
|
-
result_elements = format_tool_result_compact(
|
|
519
|
-
tr['name'],
|
|
520
|
-
content,
|
|
521
|
-
max_lines=10,
|
|
522
|
-
)
|
|
523
|
-
for elem in result_elements:
|
|
524
|
-
console.print(elem)
|
|
525
|
-
|
|
526
|
-
# Render any sub-agents not already shown via task tool calls
|
|
527
|
-
for sa in state.subagents:
|
|
528
|
-
if sa.name not in shown_sa_names and (sa.tool_calls or sa.is_active):
|
|
529
|
-
for elem in _render_subagent_section(sa, compact=True):
|
|
530
|
-
console.print(elem)
|
|
531
|
-
|
|
532
|
-
console.print()
|
|
533
|
-
|
|
534
|
-
# Task List panel in final output
|
|
535
|
-
if state.todo_items:
|
|
536
|
-
console.print(_render_todo_panel(state.todo_items))
|
|
537
|
-
console.print()
|
|
538
|
-
|
|
539
|
-
if state.response_text:
|
|
540
|
-
# Strip trailing standalone "..." lines
|
|
541
|
-
clean_response = state.response_text.rstrip()
|
|
542
|
-
while clean_response.endswith("\n...") or clean_response.rstrip() == "...":
|
|
543
|
-
clean_response = clean_response.rstrip().removesuffix("...").rstrip()
|
|
544
|
-
console.print()
|
|
545
|
-
console.print(Markdown(clean_response or state.response_text))
|
|
546
|
-
console.print()
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
# ---------------------------------------------------------------------------
|
|
550
|
-
# Async-to-sync bridge
|
|
551
|
-
# ---------------------------------------------------------------------------
|
|
552
|
-
|
|
553
|
-
def _run_streaming(
|
|
554
|
-
agent: Any,
|
|
555
|
-
message: str,
|
|
556
|
-
thread_id: str,
|
|
557
|
-
show_thinking: bool,
|
|
558
|
-
interactive: bool,
|
|
559
|
-
) -> None:
|
|
560
|
-
"""Run async streaming and render with Rich Live display.
|
|
561
|
-
|
|
562
|
-
Bridges the async stream_agent_events() into synchronous Rich Live rendering
|
|
563
|
-
using asyncio.run().
|
|
564
|
-
|
|
565
|
-
Args:
|
|
566
|
-
agent: Compiled agent graph
|
|
567
|
-
message: User message
|
|
568
|
-
thread_id: Thread ID
|
|
569
|
-
show_thinking: Whether to show thinking panel
|
|
570
|
-
interactive: If True, use simplified final display (no panel)
|
|
571
|
-
"""
|
|
572
|
-
state = StreamState()
|
|
573
|
-
|
|
574
|
-
async def _consume() -> None:
|
|
575
|
-
async for event in stream_agent_events(agent, message, thread_id):
|
|
576
|
-
event_type = state.handle_event(event)
|
|
577
|
-
live.update(create_streaming_display(
|
|
578
|
-
**state.get_display_args(),
|
|
579
|
-
show_thinking=show_thinking,
|
|
580
|
-
))
|
|
581
|
-
if event_type in (
|
|
582
|
-
"tool_call", "tool_result",
|
|
583
|
-
"subagent_start", "subagent_tool_call",
|
|
584
|
-
"subagent_tool_result", "subagent_end",
|
|
585
|
-
):
|
|
586
|
-
live.refresh()
|
|
587
|
-
|
|
588
|
-
with Live(console=console, refresh_per_second=10, transient=True) as live:
|
|
589
|
-
live.update(create_streaming_display(is_waiting=True))
|
|
590
|
-
asyncio.run(_consume())
|
|
591
|
-
|
|
592
|
-
if interactive:
|
|
593
|
-
display_final_results(
|
|
594
|
-
state,
|
|
595
|
-
thinking_max_length=500,
|
|
596
|
-
show_thinking=False,
|
|
597
|
-
show_tools=True,
|
|
598
|
-
)
|
|
599
|
-
else:
|
|
600
|
-
console.print()
|
|
601
|
-
display_final_results(
|
|
602
|
-
state,
|
|
603
|
-
show_tools=True,
|
|
604
|
-
)
|