feat(agent): add streaming groundwork for future TUI

Preserve the provider and agent-loop streaming primitives plus the CLI experiment scaffolding so this work can be resumed later without blocking urgent bug fixes on main.

Made-with: Cursor
This commit is contained in:
Xubin Ren
2026-03-23 10:20:41 +08:00
committed by Xubin Ren
parent 5fd66cae5c
commit e79b9f4a83
5 changed files with 268 additions and 90 deletions
+26 -13
View File
@@ -207,6 +207,10 @@ class _ThinkingSpinner:
self._active = False
if self._spinner:
self._spinner.stop()
# Force-clear the spinner line: Rich Live's transient cleanup
# occasionally loses a race with its own render thread.
console.file.write("\033[2K\r")
console.file.flush()
return False
@contextmanager
@@ -214,6 +218,8 @@ class _ThinkingSpinner:
"""Temporarily stop spinner while printing progress."""
if self._spinner and self._active:
self._spinner.stop()
console.file.write("\033[2K\r")
console.file.flush()
try:
yield
finally:
@@ -770,16 +776,25 @@ def agent(
async def run_once():
nonlocal _thinking
_thinking = _ThinkingSpinner(enabled=not logs)
with _thinking:
with _thinking or nullcontext():
response = await agent_loop.process_direct(
message, session_id, on_progress=_cli_progress,
message, session_id,
on_progress=_cli_progress,
)
_thinking = None
_print_agent_response(
response.content if response else "",
render_markdown=markdown,
metadata=response.metadata if response else None,
)
if _thinking:
_thinking.__exit__(None, None, None)
_thinking = None
if response and response.content:
_print_agent_response(
response.content,
render_markdown=markdown,
metadata=response.metadata,
)
else:
console.print()
await agent_loop.close_mcp()
asyncio.run(run_once())
@@ -820,6 +835,7 @@ def agent(
while True:
try:
msg = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
if msg.metadata.get("_progress"):
is_tool_hint = msg.metadata.get("_tool_hint", False)
ch = agent_loop.channels_config
@@ -834,6 +850,7 @@ def agent(
if msg.content:
turn_response.append((msg.content, dict(msg.metadata or {})))
turn_done.set()
elif msg.content:
await _print_interactive_response(
msg.content,
@@ -872,11 +889,7 @@ def agent(
content=user_input,
))
nonlocal _thinking
_thinking = _ThinkingSpinner(enabled=not logs)
with _thinking:
await turn_done.wait()
_thinking = None
await turn_done.wait()
if turn_response:
content, meta = turn_response[0]