From df2c837e252b76a8d3a91bd8a64b8987089f6892 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Mon, 23 Feb 2026 07:12:41 +0000 Subject: [PATCH] feat(channels): split send_progress into send_progress + send_tool_hints --- nanobot/agent/loop.py | 12 +++++++----- nanobot/channels/manager.py | 7 +++++-- nanobot/cli/commands.py | 19 +++++++++++++++++-- nanobot/config/schema.py | 3 ++- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 0bd05a8..cd67bdc 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -27,7 +27,7 @@ from nanobot.providers.base import LLMProvider from nanobot.session.manager import Session, SessionManager if TYPE_CHECKING: - from nanobot.config.schema import ExecToolConfig + from nanobot.config.schema import ChannelsConfig, ExecToolConfig from nanobot.cron.service import CronService @@ -59,9 +59,11 @@ class AgentLoop: restrict_to_workspace: bool = False, session_manager: SessionManager | None = None, mcp_servers: dict | None = None, + channels_config: ChannelsConfig | None = None, ): from nanobot.config.schema import ExecToolConfig self.bus = bus + self.channels_config = channels_config self.provider = provider self.workspace = workspace self.model = model or provider.get_default_model() @@ -172,7 +174,7 @@ class AgentLoop: async def _run_agent_loop( self, initial_messages: list[dict], - on_progress: Callable[[str], Awaitable[None]] | None = None, + on_progress: Callable[..., Awaitable[None]] | None = None, ) -> tuple[str | None, list[str]]: """Run the agent iteration loop. Returns (final_content, tools_used).""" messages = initial_messages @@ -196,8 +198,7 @@ class AgentLoop: clean = self._strip_think(response.content) if clean: await on_progress(clean) - else: - await on_progress(self._tool_hint(response.tool_calls)) + await on_progress(self._tool_hint(response.tool_calls), tool_hint=True) tool_call_dicts = [ { @@ -383,9 +384,10 @@ class AgentLoop: channel=msg.channel, chat_id=msg.chat_id, ) - async def _bus_progress(content: str) -> None: + async def _bus_progress(content: str, *, tool_hint: bool = False) -> None: meta = dict(msg.metadata or {}) meta["_progress"] = True + meta["_tool_hint"] = tool_hint await self.bus.publish_outbound(OutboundMessage( channel=msg.channel, chat_id=msg.chat_id, content=content, metadata=meta, )) diff --git a/nanobot/channels/manager.py b/nanobot/channels/manager.py index 8a03883..77b7294 100644 --- a/nanobot/channels/manager.py +++ b/nanobot/channels/manager.py @@ -193,8 +193,11 @@ class ChannelManager: timeout=1.0 ) - if msg.metadata.get("_progress") and not self.config.channels.send_progress: - continue + if msg.metadata.get("_progress"): + if msg.metadata.get("_tool_hint") and not self.config.channels.send_tool_hints: + continue + if not msg.metadata.get("_tool_hint") and not self.config.channels.send_progress: + continue channel = self.channels.get(msg.channel) if channel: diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index f1f9b30..fcbd370 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -368,6 +368,7 @@ def gateway( restrict_to_workspace=config.tools.restrict_to_workspace, session_manager=session_manager, mcp_servers=config.tools.mcp_servers, + channels_config=config.channels, ) # Set cron callback (needs agent) @@ -484,6 +485,7 @@ def agent( cron_service=cron, restrict_to_workspace=config.tools.restrict_to_workspace, mcp_servers=config.tools.mcp_servers, + channels_config=config.channels, ) # Show spinner when logs are off (no output to miss); skip when logs are on @@ -494,7 +496,12 @@ def agent( # Animated spinner is safe to use with prompt_toolkit input handling return console.status("[dim]nanobot is thinking...[/dim]", spinner="dots") - async def _cli_progress(content: str) -> None: + async def _cli_progress(content: str, *, tool_hint: bool = False) -> None: + ch = agent_loop.channels_config + if ch and tool_hint and not ch.send_tool_hints: + return + if ch and not tool_hint and not ch.send_progress: + return console.print(f" [dim]↳ {content}[/dim]") if message: @@ -535,7 +542,14 @@ def agent( try: msg = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0) if msg.metadata.get("_progress"): - console.print(f" [dim]↳ {msg.content}[/dim]") + is_tool_hint = msg.metadata.get("_tool_hint", False) + ch = agent_loop.channels_config + if ch and is_tool_hint and not ch.send_tool_hints: + pass + elif ch and not is_tool_hint and not ch.send_progress: + pass + else: + console.print(f" [dim]↳ {msg.content}[/dim]") elif not turn_done.is_set(): if msg.content: turn_response.append(msg.content) @@ -961,6 +975,7 @@ def cron_run( exec_config=config.tools.exec, restrict_to_workspace=config.tools.restrict_to_workspace, mcp_servers=config.tools.mcp_servers, + channels_config=config.channels, ) store_path = get_data_dir() / "cron" / "jobs.json" diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index fc9fede..9265602 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -168,7 +168,8 @@ class QQConfig(Base): class ChannelsConfig(Base): """Configuration for chat channels.""" - send_progress: bool = False + send_progress: bool = True # stream agent's text progress to the channel + send_tool_hints: bool = False # stream tool-call hints (e.g. read_file("…")) whatsapp: WhatsAppConfig = Field(default_factory=WhatsAppConfig) telegram: TelegramConfig = Field(default_factory=TelegramConfig) discord: DiscordConfig = Field(default_factory=DiscordConfig)