Commit Graph
39 Commits
Author SHA1 Message Date
hamb1yandXubin Ren 7d09f1cd9e Add Discord model slash command 2026-05-28 15:48:50 +08:00
chengyongruandXubin Ren 4c4a9ae590 feat(pairing): chat-native DM sender approval
Replace the file-editing onboarding workflow with a chat-native pairing flow:

- New pairing store (nanobot/pairing/store.py) persists approved senders
  and pending codes in ~/.nanobot/pairing.json.
- DM messages from unknown senders receive a short pairing code instead of
  silent denial. Group chats remain silently ignored.
- Existing allowFrom semantics are fully preserved; approved pairing users
  are merged at runtime so no config migration is needed.
- nanobot pairing list/approve/deny/revoke CLI commands for bootstrap and
  emergency management.
- /pairing slash commands intercepted in-channel so owners can approve
  senders without leaving the chat.
- is_dm flag added to BaseChannel._handle_message; Telegram, Discord and
  WebSocket updated to pass it.

Closes #3768
2026-05-15 15:46:44 +08:00
chengyongruandXubin Ren 536c456e5e fix(channels): restore bound logger in discord and websocket
PR introduced module-level logger in static methods, which drops
the channel context bound by BaseChannel.__init__. Revert to
self._channel.logger / self.logger to preserve log labels.

Also remove @staticmethod since these methods legitimately need
instance access (F821 was the real issue, not the logger source).
2026-05-07 13:07:22 +08:00
yorkhellenandXubin Ren a2f5de6838 refactor: fix import order for logger in discord.py 2026-05-07 13:07:22 +08:00
yorkhellenandXubin Ren 10a0bb0fb3 refactor: use module-level logger in static methods 2026-05-07 13:07:22 +08:00
yorkhellenandXubin Ren 4773589685 fix: F821 undefined name errors in channels 2026-05-07 13:07:22 +08:00
chengyongruandXubin Ren 05e0106592 refactor(logging): preserve tracebacks and add channel context
- Preserve tracebacks: logger.error in except blocks → logger.exception
- Channel context: BaseChannel injects self.logger = logger.bind(channel=name)
- Third-party bridge: redirect_lib_logging() replaces ad-hoc stdlib-to-loguru bridges
- Log levels: network timeouts downgraded from ERROR → WARNING
- Fix --verbose flag to actually work with loguru (set handler to DEBUG)
2026-05-06 21:17:45 +08:00
Jack LuandXubin Ren d9800ecdd2 refactor: replace try-except blocks with contextlib.suppress for cleaner error handling across multiple files 2026-05-01 19:30:11 +08:00
Xubin RenandXubin Ren 2b886ffd1f fix(command): expose history in chat command menus
Made-with: Cursor
2026-04-27 18:23:35 +08:00
Bongjin Lee 93ca791ac6 fix(discord): full thread support with session isolation and allowlist enforcement
Discord threads use their own channel IDs, so allowChannels was blocking
thread replies unless each thread ID was listed explicitly.

- Include the thread parent channel ID as an allowlist candidate
- Enforce allow_channels on slash commands (previously bypassed)
- Show parent channel ID in runtime context, reply to the thread
- Fix subagent cancel key via effective_key propagation
- Detect bot mentions via raw_mentions and reply-to-bot references
- Cache seen thread channels for outbound delivery
- Ignore system messages that become empty prompts
2026-04-23 04:05:39 +09:00
chengyongruandXubin Ren d4e34f8c67 fix(commands): intercept non-priority commands during active turn
Non-priority slash commands (e.g. /new, /help, /dream-log) arriving
while a session has an active LLM turn were silently queued into the
pending injection buffer and later injected as raw user messages into
the LLM conversation. This caused the model to respond to "/new" as
plain text instead of executing the command.

Root cause: the run() loop only checked priority commands (/stop,
/restart, /status) before routing messages to the pending queue. All
other command tiers (exact, prefix) bypassed command dispatch entirely.

Changes:
- Add CommandRouter.is_dispatchable_command() to match exact/prefix
  tiers, mirroring the existing is_priority() pattern.
- In run(), intercept dispatchable commands before pending queue
  insertion and dispatch them directly via _dispatch_command_inline().
- Extract _cancel_active_tasks() from cmd_stop for reuse; cmd_new now
  cancels active tasks before clearing the session to prevent shared
  mutable state corruption from concurrent asyncio coroutines.
- Update /new semantics: stops active task first, then clears session.
- Update documentation in help text, docs, and Discord command list.
2026-04-21 21:50:37 +08:00
Alfredo ArenasandXubin Ren 3fd24c72fd fix(discord): allow bot-to-bot messaging, only drop self-loops (#3217)
Previously the Discord channel dropped every message from any bot
account via `if message.author.bot`, which prevented legitimate
multi-agent setups (one bot asking another for help, bot-to-bot
@mentions, etc.) from working.

Narrow the guard to only drop messages from this bot's own account
by comparing against self._bot_user_id (already populated in on_ready).
Self-loop protection is preserved — each bot instance still ignores
its own outbound messages.

Co-authored with Claude Opus 4.7
2026-04-19 23:32:40 +08:00
48d430bf5e feat: add channel-based filtering for Discord
Add `allow_channels` config option to DiscordConfig that restricts
bot responses to specific Discord channels. When the list is empty
(default), the bot responds in all channels (backward compatible).

- Add `allow_channels: list[str]` field to DiscordConfig schema
- Add channel ID check in _handle_message_create after user filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 02:14:33 +08:00
Leo fuandXubin Ren 2c0cd085a4 fix(discord): remove duplicate channel_id assignment in message handler
channel_id is already assigned from self._channel_key(message.channel)
earlier in the same function. The second identical assignment on line 453
is dead code left over from a copy-paste.
2026-04-16 02:11:13 +08:00
bahtyaandXubin Ren fa98524944 fix(channels): prevent retry amplification and silent message loss across channels
Audited all channel implementations for overly broad exception handling
that causes retry amplification or silent message loss during network
errors. This is the same class of bug as #3050 (Telegram _send_text).

Fixes by channel:

Telegram (send_delta):
- _stream_end path used except Exception for HTML edit fallback
- Network errors (TimedOut, NetworkError) triggered redundant plain
  text edit, doubling connection demand during pool exhaustion
- Changed to except BadRequest, matching the _send_text fix

Discord:
- send() caught all exceptions without re-raising
- ChannelManager._send_with_retry() saw successful return, never retried
- Messages silently dropped on any send failure
- Added raise after error logging

DingTalk:
- _send_batch_message() returned False on all exceptions including
  network errors — no retry, fallback text sent unnecessarily
- _read_media_bytes() and _upload_media() swallowed transport errors,
  causing _send_media_ref() to cascade through doomed fallback attempts
- Added except httpx.TransportError handlers that re-raise immediately

WeChat:
- Media send failure triggered text fallback even for network errors
- During network issues: 3×(media + text) = 6 API calls per message
- Added specific catches: TimeoutException/TransportError re-raise,
  5xx HTTPStatusError re-raises, 4xx falls back to text

QQ:
- _send_media() returned False on all exceptions
- Network errors triggered fallback text instead of retry
- Added except (aiohttp.ClientError, OSError) that re-raises

Tests: 331 passed (283 existing + 48 new across 5 channel test files)

Fixes: #3054
Related: #3050, #3053
2026-04-13 00:30:45 +08:00
Xubin RenandXubin Ren 69d748bf8f Merge origin/main; warn on partial proxy credentials; add only-password test
- Merged latest main (no conflicts)
- Added warning log when only one of proxy_username/proxy_password is set
- Added test_start_no_proxy_auth_when_only_password for coverage parity

Made-with: Cursor
2026-04-09 23:54:11 +08:00
JonasandXubin Ren 7506af7104 feat(channel): add proxy support for Discord channel
- Add proxy, proxy_username, proxy_password fields to DiscordConfig
- Pass proxy and proxy_auth to discord.Client
- Add aiohttp.BasicAuth when credentials are provided
- Add tests for proxy configuration scenarios
2026-04-09 23:54:11 +08:00
SHLE1andXubin Ren e49b6c0c96 fix(discord): enable streaming replies 2026-04-09 00:24:11 +08:00
8956df3668 feat(discord): configurable read receipt + subagent working indicator (#2330)
* feat(discord): channel-side read receipt and subagent indicator

- Add 👀 reaction on message receipt, removed after bot reply
- Add 🔧 reaction on first progress message, removed on final reply
- Both managed purely in discord.py channel layer, no subagent.py changes
- Config: read_receipt_emoji, subagent_emoji with sensible defaults

Addresses maintainer feedback on HKUDS/nanobot#2330

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(discord): add both reactions on inbound, not on progress

_progress flag is for streaming chunks, not subagent lifecycle.
Add 👀 + 🔧 immediately on message receipt, clear both on final reply.

* fix: remove stale _subagent_active reference in _clear_reactions

* fix(discord): clean up reactions on message handling failure

Previously, if _handle_message raised an exception, pending reactions
(read receipt + subagent indicator) would remain on the user's message
indefinitely since send() — which handles normal cleanup — would never
be called.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(discord): replace subagent_emoji with delayed working indicator

- Rename subagent_emoji → working_emoji (honest naming: not tied to
  subagent lifecycle)
- Add working_emoji_delay (default 2s) — cosmetic delay so 🔧 appears
  after 👀, cancelled if bot replies before delay fires
- Clean up: cancel pending task + remove both reactions on reply/error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:26:07 +08:00
Paresh MathurXubin RenPares Mathurgemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
0506e6c1c1 feat(discord): Use discord.py for stable discord channel (#2486)
Co-authored-by: Pares Mathur <paresh.2047@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-31 19:26:07 +08:00
Xubin RenandXubin Ren dbdb43faff feat: channel plugin architecture with decoupled configs
- Add plugin discovery via Python entry_points (group: nanobot.channels)
- Move 11 channel Config classes from schema.py into their own channel modules
- ChannelsConfig now only keeps send_progress + send_tool_hints (extra=allow)
- Each built-in channel parses dict->Pydantic in __init__, zero internal changes
- All channels implement default_config() for onboard auto-population
- nanobot onboard injects defaults for all discovered channels (built-in + plugins)
- Add nanobot plugins list CLI command
- Add Channel Plugin Guide (docs/CHANNEL_PLUGIN_GUIDE.md)
- Fully backward compatible: existing config.json and sessions work as-is
- 340 tests pass, zero regressions
2026-03-14 16:13:38 +08:00
Re-bin 254cfd48ba refactor: auto-discover channels via pkgutil, eliminate hardcoded registry 2026-03-11 14:23:19 +00:00
Re-bin 20dfaa5d34 refactor: unify instance path resolution and preserve workspace override 2026-03-08 02:58:25 +00:00
Re-bin c81d32c40f fix(discord): handle attachment reply fallback 2026-03-07 04:07:25 +00:00
SLAR_Edge a25923b793 feat: enhance message sending to include file attachments in Discord API 2026-03-06 17:10:53 +08:00
Re-bin 7e9616cbd3 merge origin/main into pr-1567 2026-03-06 06:51:28 +00:00
Re-bin 06fcd2cc3f fix(discord): correct group_policy default to mention and style cleanup 2026-03-05 14:33:14 +00:00
suger-m 323e5f22cc refactor(channels): extract split_message utility to reduce code duplication
Extract the _split_message function from discord.py and telegram.py
into a shared utility function in utils/helpers.py.

Changes:
- Add split_message() to nanobot/utils/helpers.py with configurable max_len
- Update Discord channel to use shared utility (2000 char limit)
- Update Telegram channel to use shared utility (4000 char limit)
- Remove duplicate implementations from both channels

Benefits:
- Reduces code duplication
- Centralizes message splitting logic for easier maintenance
- Makes the function reusable for future channels

The function splits content into chunks within max_len, preferring
to break at newlines or spaces rather than mid-word.
2026-03-05 17:16:47 +08:00
Joel Chan e9d023f52c feat(discord): add group policy to control group respond behaviour 2026-03-02 12:16:49 +08:00
JK_Lu 977ca725f2 style: unify code formatting and import order
- Remove trailing whitespace and normalize blank lines
- Unify string quotes and line breaks for long lines
- Sort imports alphabetically across modules
2026-02-28 20:55:43 +08:00
Nikolas de Hor 4303026e0d fix: break Discord typing loop on persistent HTTP failure
The typing indicator loop catches all exceptions with bare
except/pass, so a permanent HTTP failure (client closed, auth
error, etc.) causes the loop to spin every 8 seconds doing
nothing until the channel is explicitly stopped.

Log the error and exit the loop instead, letting the task
clean up naturally.
2026-02-22 22:01:16 -03:00
Re-bin b853222c87 style: trim _send_payload docstring 2026-02-20 15:26:12 +00:00
Nikolas de Hor 4cbd857250 fix: handle edge cases in message splitting and send failure
- _split_message: return empty list for empty/None content instead
  of a list with one empty string (Discord rejects empty content)
- _split_message: use pos <= 0 fallback to prevent empty chunks
  when content starts with a newline or space
- _send_payload: return bool to indicate success/failure
- send: abort remaining chunks when a chunk fails to send,
  preventing partial/corrupted message delivery
2026-02-20 10:09:04 -03:00
Nikolas de Hor 4c75e1673f fix: split Discord messages exceeding 2000-character limit
Discord's API rejects messages longer than 2000 characters with HTTP 400.
Previously, long agent responses were silently lost after retries exhausted.

Adds _split_message() (matching Telegram's approach) to chunk content at
line boundaries before sending. Only the first chunk carries the reply
reference. Retry logic extracted to _send_payload() for reuse across chunks.

Closes #898
2026-02-20 09:55:22 -03:00
Re-bin 37252a4226 fix: complete loguru native formatting migration across all files 2026-02-20 07:55:34 +00:00
Nikolas de Hor 53b83a38e2 fix: use loguru native formatting to prevent KeyError on messages containing curly braces
Closes #857
2026-02-19 17:19:36 -03:00
Re-bin 8a1d7c76d2 refactor: simplify discord channel and improve setup docs 2026-02-06 07:04:10 +00:00
Anunay Aatipamula bab464df5f feat(discord): implement typing indicator functionality
- Add methods to manage typing indicators in Discord channels.
- Introduce periodic typing notifications while sending messages.
- Ensure proper cleanup of typing tasks on channel closure.
2026-02-02 19:01:46 +05:30
Anunay Aatipamula ba6c4b748f feat(discord): add Discord channel support
- Implement Discord channel functionality with websocket integration.
- Update configuration schema to include Discord settings.
- Enhance README with setup instructions for Discord integration.
- Modify channel manager to initialize Discord channel if enabled.
- Update CLI status command to display Discord channel status.
2026-02-02 18:41:17 +05:30