feat(channels): split send_progress into send_progress + send_tool_hints

This commit is contained in:
Re-bin
2026-02-23 07:12:41 +00:00
parent c20b867497
commit df2c837e25
4 changed files with 31 additions and 10 deletions

View File

@@ -27,7 +27,7 @@ from nanobot.providers.base import LLMProvider
from nanobot.session.manager import Session, SessionManager from nanobot.session.manager import Session, SessionManager
if TYPE_CHECKING: if TYPE_CHECKING:
from nanobot.config.schema import ExecToolConfig from nanobot.config.schema import ChannelsConfig, ExecToolConfig
from nanobot.cron.service import CronService from nanobot.cron.service import CronService
@@ -59,9 +59,11 @@ class AgentLoop:
restrict_to_workspace: bool = False, restrict_to_workspace: bool = False,
session_manager: SessionManager | None = None, session_manager: SessionManager | None = None,
mcp_servers: dict | None = None, mcp_servers: dict | None = None,
channels_config: ChannelsConfig | None = None,
): ):
from nanobot.config.schema import ExecToolConfig from nanobot.config.schema import ExecToolConfig
self.bus = bus self.bus = bus
self.channels_config = channels_config
self.provider = provider self.provider = provider
self.workspace = workspace self.workspace = workspace
self.model = model or provider.get_default_model() self.model = model or provider.get_default_model()
@@ -172,7 +174,7 @@ class AgentLoop:
async def _run_agent_loop( async def _run_agent_loop(
self, self,
initial_messages: list[dict], initial_messages: list[dict],
on_progress: Callable[[str], Awaitable[None]] | None = None, on_progress: Callable[..., Awaitable[None]] | None = None,
) -> tuple[str | None, list[str]]: ) -> tuple[str | None, list[str]]:
"""Run the agent iteration loop. Returns (final_content, tools_used).""" """Run the agent iteration loop. Returns (final_content, tools_used)."""
messages = initial_messages messages = initial_messages
@@ -196,8 +198,7 @@ class AgentLoop:
clean = self._strip_think(response.content) clean = self._strip_think(response.content)
if clean: if clean:
await on_progress(clean) await on_progress(clean)
else: await on_progress(self._tool_hint(response.tool_calls), tool_hint=True)
await on_progress(self._tool_hint(response.tool_calls))
tool_call_dicts = [ tool_call_dicts = [
{ {
@@ -383,9 +384,10 @@ class AgentLoop:
channel=msg.channel, chat_id=msg.chat_id, 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 = dict(msg.metadata or {})
meta["_progress"] = True meta["_progress"] = True
meta["_tool_hint"] = tool_hint
await self.bus.publish_outbound(OutboundMessage( await self.bus.publish_outbound(OutboundMessage(
channel=msg.channel, chat_id=msg.chat_id, content=content, metadata=meta, channel=msg.channel, chat_id=msg.chat_id, content=content, metadata=meta,
)) ))

View File

@@ -193,8 +193,11 @@ class ChannelManager:
timeout=1.0 timeout=1.0
) )
if msg.metadata.get("_progress") and not self.config.channels.send_progress: if msg.metadata.get("_progress"):
continue 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) channel = self.channels.get(msg.channel)
if channel: if channel:

View File

@@ -368,6 +368,7 @@ def gateway(
restrict_to_workspace=config.tools.restrict_to_workspace, restrict_to_workspace=config.tools.restrict_to_workspace,
session_manager=session_manager, session_manager=session_manager,
mcp_servers=config.tools.mcp_servers, mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
) )
# Set cron callback (needs agent) # Set cron callback (needs agent)
@@ -484,6 +485,7 @@ def agent(
cron_service=cron, cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace, restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers, 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 # 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 # Animated spinner is safe to use with prompt_toolkit input handling
return console.status("[dim]nanobot is thinking...[/dim]", spinner="dots") 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]") console.print(f" [dim]↳ {content}[/dim]")
if message: if message:
@@ -535,7 +542,14 @@ def agent(
try: try:
msg = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0) msg = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
if msg.metadata.get("_progress"): 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(): elif not turn_done.is_set():
if msg.content: if msg.content:
turn_response.append(msg.content) turn_response.append(msg.content)
@@ -961,6 +975,7 @@ def cron_run(
exec_config=config.tools.exec, exec_config=config.tools.exec,
restrict_to_workspace=config.tools.restrict_to_workspace, restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers, mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
) )
store_path = get_data_dir() / "cron" / "jobs.json" store_path = get_data_dir() / "cron" / "jobs.json"

View File

@@ -168,7 +168,8 @@ class QQConfig(Base):
class ChannelsConfig(Base): class ChannelsConfig(Base):
"""Configuration for chat channels.""" """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) whatsapp: WhatsAppConfig = Field(default_factory=WhatsAppConfig)
telegram: TelegramConfig = Field(default_factory=TelegramConfig) telegram: TelegramConfig = Field(default_factory=TelegramConfig)
discord: DiscordConfig = Field(default_factory=DiscordConfig) discord: DiscordConfig = Field(default_factory=DiscordConfig)