feat(cli): add real-time log streaming in non-interactive mode

- Non-interactive mode now streams logs to stdout with color-coded levels
- Add proper async cleanup when shutting down
- Fix type annotations in coze and deerflow agent runners
This commit is contained in:
LIghtJUNction
2026-03-23 17:21:05 +08:00
parent bf21f1a499
commit 99c66c2410
4 changed files with 67 additions and 10 deletions

View File

@@ -323,12 +323,69 @@ def run(
)
)
else:
click.echo("AstrBot is running...")
if backend_only:
click.echo("Visit the dashboard at : https://dash.astrbot.men/")
click.echo("Backend Requests : localhost or based on https")
# Non-interactive mode: run with stdout log streaming
async def run_with_logging() -> None:
from astrbot.core import LogBroker, LogManager, db_helper, logger
from astrbot.core.initial_loader import InitialLoader
asyncio.run(run_astrbot(astrbot_root))
if (
os.environ.get("ASTRBOT_DASHBOARD_ENABLE", os.environ.get("DASHBOARD_ENABLE"))
== "True"
):
await DashboardManager().ensure_installed(astrbot_root)
log_broker = LogBroker()
LogManager.set_queue_handler(logger, log_broker)
# Register a stdout subscriber for real-time log streaming
log_queue = log_broker.register()
db = db_helper
initial_loader = InitialLoader(db, log_broker)
# Start a task to stream logs to stdout
async def stream_logs() -> None:
"""Stream logs from LogBroker to stdout."""
import logging
while True:
try:
log_entry = await asyncio.wait_for(log_queue.get(), timeout=0.5)
# Format: [LEVEL] message
level = log_entry.get("level_name", "INFO")
message = log_entry.get("message", "")
if message:
level_color = {
"DEBUG": "cyan",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "red",
}.get(level, "white")
click.secho(f"[{level}]", fg=level_color, bold=False, nl=False)
click.echo(f" {message}")
except asyncio.TimeoutError:
continue
except asyncio.CancelledError:
break
# Start streaming task
stream_task = asyncio.create_task(stream_logs())
try:
await initial_loader.start()
finally:
stream_task.cancel()
try:
await stream_task
except asyncio.CancelledError:
pass
click.echo("AstrBot is running... (streaming logs)")
if backend_only:
click.echo("Dashboard: https://dash.astrbot.men/")
click.echo("Backend: localhost or based on https")
asyncio.run(run_with_logging())
except KeyboardInterrupt:
click.echo("AstrBot has been shut down.")
except Timeout:

View File

@@ -86,7 +86,7 @@ class CozeAgentRunner(BaseAgentRunner[TContext]):
self.file_id_cache: dict[str, dict[str, str]] = {}
@override
async def step(self):
async def step(self) -> AsyncGenerator[AgentResponse, None]:
"""
执行 Coze Agent 的一个步骤
"""

View File

@@ -23,8 +23,8 @@ from astrbot.core.utils.config_number import coerce_int_config
from ...hooks import BaseAgentRunHooks
from ...response import AgentResponseData
from ...run_context import ContextWrapper, TContext
from ...tool_executor import BaseFunctionToolExecutor
from ..base import AgentResponse, AgentState, BaseAgentRunner
from ..tool_executor import BaseFunctionToolExecutor
from .constants import DEERFLOW_SESSION_PREFIX, DEERFLOW_THREAD_ID_KEY
from .deerflow_api_client import DeerFlowAPIClient
from .deerflow_content_mapper import (

View File

@@ -70,7 +70,7 @@ class BrowserExecTool(FunctionTool):
async def call(
self,
context: ContextWrapper[AstrAgentContext],
cmd: str,
cmd: str = "",
timeout: int = 30,
description: str | None = None,
tags: str | None = None,
@@ -133,7 +133,7 @@ class BrowserBatchExecTool(FunctionTool):
async def call(
self,
context: ContextWrapper[AstrAgentContext],
commands: list[str],
commands: list[str] | None = None,
timeout: int = 60,
stop_on_error: bool = True,
description: str | None = None,
@@ -182,7 +182,7 @@ class RunBrowserSkillTool(FunctionTool):
async def call(
self,
context: ContextWrapper[AstrAgentContext],
skill_key: str,
skill_key: str = "",
timeout: int = 60,
stop_on_error: bool = True,
include_trace: bool = False,