diff --git a/docs/channel-package-guide.md b/docs/channel-package-guide.md index d53904b3..1ea4cb97 100644 --- a/docs/channel-package-guide.md +++ b/docs/channel-package-guide.md @@ -618,7 +618,7 @@ async def send(self, msg: OutboundMessage) -> None: await self._send_message(msg.chat_id, msg.content, media=msg.media) ``` -Tool hints are off by default for most channels. Users can enable them globally or per channel: +Tool hints are on by default. Users can disable them globally or per channel: ```json { @@ -626,7 +626,7 @@ Tool hints are off by default for most channels. Users can enable them globally "sendToolHints": true, "webhook": { "enabled": true, - "sendToolHints": true + "sendToolHints": false } } } diff --git a/docs/configuration.md b/docs/configuration.md index 3f461a07..ed80cf62 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1555,7 +1555,7 @@ Global settings that apply to all channels. Configure under the `channels` secti { "channels": { "sendProgress": true, - "sendToolHints": false, + "sendToolHints": true, "extractDocumentText": true, "sendMaxRetries": 3, "telegram": { @@ -1568,7 +1568,7 @@ Global settings that apply to all channels. Configure under the `channels` secti | Setting | Default | Description | |---------|---------|-------------| | `sendProgress` | `true` | Stream agent's text progress to the channel | -| `sendToolHints` | `false` | Stream tool-call hints (e.g. `read_file("…")`) | +| `sendToolHints` | `true` | Stream tool-call hints (e.g. `read_file("…")`) | | `showReasoning` | `true` | Allow channels to surface model reasoning/thinking content (DeepSeek-R1 `reasoning_content`, Anthropic `thinking_blocks`, inline `` tags). Reasoning flows as a dedicated stream with `_reasoning_delta` / `_reasoning_end` markers — channels override `send_reasoning_delta` / `send_reasoning_end` to render in-place updates. Even with `true`, channels without those overrides stay no-op silently. Currently surfaced on CLI and WebSocket/WebUI (italic shimmer header, auto-collapses after the stream ends); Telegram / Slack / Discord / Feishu / WeChat / Matrix / Mattermost keep the base no-op until their bubble UI is adapted. Independent of `sendProgress`. | | `extractDocumentText` | `true` | Extract supported document/text attachments into the model prompt. PDF, DOCX, XLSX, and PPTX readers are included in the standard installation. Set to `false` to keep document content out of the prompt and include attachment path references instead. | | `sendMaxRetries` | `3` | Max delivery attempts per outbound message, including the initial send (0-10 configured, minimum 1 actual attempt) | @@ -1581,10 +1581,11 @@ Global settings that apply to all channels. Configure under the `channels` secti { "channels": { "sendProgress": true, - "sendToolHints": false, + "sendToolHints": true, "telegram": { "enabled": true, - "sendProgress": false + "sendProgress": false, + "sendToolHints": false }, "websocket": { "enabled": true, diff --git a/nanobot/channels/base.py b/nanobot/channels/base.py index 4dcbbf96..c4f51b7a 100644 --- a/nanobot/channels/base.py +++ b/nanobot/channels/base.py @@ -29,7 +29,7 @@ class BaseChannel(ABC): name: str = "base" display_name: str = "Base" send_progress: bool = True - send_tool_hints: bool = False + send_tool_hints: bool = True show_reasoning: bool = True def __init__(self, config: Any, bus: MessageBus): diff --git a/nanobot/channels/mattermost/runtime.py b/nanobot/channels/mattermost/runtime.py index 1a229731..756daed7 100644 --- a/nanobot/channels/mattermost/runtime.py +++ b/nanobot/channels/mattermost/runtime.py @@ -56,7 +56,7 @@ class MattermostConfig(Base): react_emoji: str = "eyes" done_emoji: str = "white_check_mark" send_progress: bool = True - send_tool_hints: bool = False + send_tool_hints: bool = True dm: MattermostDMConfig = Field(default_factory=MattermostDMConfig) diff --git a/nanobot/channels/mattermost/tests/test_mattermost_channel.py b/nanobot/channels/mattermost/tests/test_mattermost_channel.py index 2371fef3..3b27ccf1 100644 --- a/nanobot/channels/mattermost/tests/test_mattermost_channel.py +++ b/nanobot/channels/mattermost/tests/test_mattermost_channel.py @@ -119,6 +119,7 @@ def test_config_defaults(): assert config.token == "" assert config.streaming is True assert config.streaming_max_chars == 16000 + assert config.send_tool_hints is True assert config.dm.enabled is True assert config.dm.policy == "open" assert config.reply_in_thread is True @@ -131,6 +132,7 @@ def test_config_camelcase_aliases(): "allowFromMatchMode": "username", "streamingMaxChars": 8000, "replyInThread": False, + "sendToolHints": False, } config = MattermostConfig.model_validate(raw) assert config.server_url == "https://mm.example.com" @@ -138,11 +140,13 @@ def test_config_camelcase_aliases(): assert config.allow_from_match_mode == "username" assert config.streaming_max_chars == 8000 assert config.reply_in_thread is False + assert config.send_tool_hints is False def test_config_default_config_classmethod(): d = MattermostChannel.default_config() assert d["enabled"] is False + assert d["sendToolHints"] is True assert d["serverUrl"] == "" assert d["token"] == "" diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index fef6c261..8d264ce6 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -30,7 +30,7 @@ class ChannelsConfig(Base): model_config = ConfigDict(extra="allow") 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("…")) + send_tool_hints: bool = True # stream tool-call hints (e.g. read_file("…")) show_reasoning: bool = True # surface model reasoning when channel implements it extract_document_text: bool = True # extract text from document attachments before sending to the model send_max_retries: int = Field(default=3, ge=0, le=10) # Max delivery attempts (initial send included) diff --git a/tests/channels/test_channel_manager_delta_coalescing.py b/tests/channels/test_channel_manager_delta_coalescing.py index 6e03df5e..de60124e 100644 --- a/tests/channels/test_channel_manager_delta_coalescing.py +++ b/tests/channels/test_channel_manager_delta_coalescing.py @@ -74,7 +74,7 @@ def bus(): @pytest.fixture def manager(config, bus): manager = ChannelManager(config, bus) - manager.channels["mock"] = MockChannel({}, bus) + manager.channels["mock"] = manager._build_channel("mock", MockChannel, {}) return manager @@ -284,14 +284,17 @@ class TestProgressFiltering: def test_progress_visibility_uses_global_defaults(self, manager): assert manager._should_send_progress("mock", tool_hint=False) is True - assert manager._should_send_progress("mock", tool_hint=True) is False + assert manager._should_send_progress("mock", tool_hint=True) is True - def test_progress_visibility_uses_channel_overrides(self, manager): - manager.channels["mock"].send_progress = False - manager.channels["mock"].send_tool_hints = True + def test_progress_visibility_uses_channel_overrides(self, manager, bus): + manager.channels["mock"] = manager._build_channel( + "mock", + MockChannel, + {"sendProgress": False, "sendToolHints": False}, + ) assert manager._should_send_progress("mock", tool_hint=False) is False - assert manager._should_send_progress("mock", tool_hint=True) is True + assert manager._should_send_progress("mock", tool_hint=True) is False def test_progress_visibility_returns_false_for_missing_channel(self, manager): assert manager._should_send_progress("nonexistent", tool_hint=False) is False diff --git a/tests/channels/test_channel_plugins.py b/tests/channels/test_channel_plugins.py index 8399242f..50ff6905 100644 --- a/tests/channels/test_channel_plugins.py +++ b/tests/channels/test_channel_plugins.py @@ -269,9 +269,12 @@ def test_channels_config_has_no_per_channel_fields(): cfg = ChannelsConfig() assert not hasattr(cfg, "telegram") assert cfg.send_progress is True - assert cfg.send_tool_hints is False + assert cfg.send_tool_hints is True assert cfg.extract_document_text is True + opted_out = ChannelsConfig.model_validate({"sendToolHints": False}) + assert opted_out.send_tool_hints is False + def test_channels_config_extract_document_text_accepts_camel_alias(): cfg = ChannelsConfig.model_validate({"extractDocumentText": False})