- Use hmac.compare_digest for timing-safe static token comparison
- Add issued token capacity limit (_MAX_ISSUED_TOKENS=10000) with 429 response
- Use atomic pop in _take_issued_token_if_valid to eliminate TOCTOU window
- Enforce TLSv1.2 minimum version for SSL connections
- Extract _safe_send helper for consistent ConnectionClosed handling
- Move connection registration after ready send to prevent out-of-order delivery
- Add HTTP-level allow_from check and client_id truncation in process_request
- Make stop() idempotent with graceful shutdown error handling
- Normalize path via validator instead of leaving raw value
- Default websocket_requires_token to True for secure-by-default behavior
- Add integration tests and ws_test_client helper
- Refactor tests to use shared _ch factory and bus fixture
Forward static location pins as [location: lat, lon] content so the
agent can respond to geo messages and pass coordinates to MCP tools.
ClosesHKUDS/nanobot#2909
Save inbound email attachments to the media directory with configurable
MIME type filtering (glob patterns like "image/*"), per-attachment size
limits, and max attachment count. Filenames are sanitized to prevent
path traversal. Controlled by allowed_attachment_types — empty (default)
means disabled, non-empty enables extraction for matching types.
If _fetch_bot_open_id returns None the exact-match path would silently
disable all @mention detection. Restore the old heuristic as a fallback.
Add 6 unit tests for _is_bot_mentioned covering both paths.
Made-with: Cursor
Use dream_log and dream_restore in Telegram's bot command menu so command registration succeeds, while still accepting the original dream-log and dream-restore forms in chat. Keep the internal command routing unchanged and add coverage for the alias normalization path.
Add ack_message config field to QQConfig (default: Processing...). When non-empty, sends an instant text reply before agent processing begins, filling the silence gap for users. Uses existing _send_text_only method; failure is logged but never blocks normal message handling.
Made-with: Cursor
1. Fix full_url path for non-image media to require AES key and skip download when missing,
instead of persisting encrypted bytes as valid media.
2. Restrict quoted media fallback trigger to only when no top-level media item exists,
not when top-level media download/decryption fails.
Fetch and cache typing tickets so the Weixin channel shows typing while nanobot is processing and clears it after the final reply.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
* feat(feishu): add streaming support via CardKit PATCH API
Implement send_delta() for Feishu channel using interactive card
progressive editing:
- First delta creates a card with markdown content and typing cursor
- Subsequent deltas throttled at 0.5s to respect 5 QPS PATCH limit
- stream_end finalizes with full formatted card (tables, rich markdown)
Also refactors _send_message_sync to return message_id (str | None)
and adds _patch_card_sync for card updates.
Includes 17 new unit tests covering streaming lifecycle, config,
card building, and edge cases.
Made-with: Cursor
* feat(feishu): close CardKit streaming_mode on stream end
Call cardkit card.settings after final content update so chat preview
leaves default [生成中...] summary (Feishu streaming docs).
Made-with: Cursor
* style: polish Feishu streaming (PEP8 spacing, drop unused test imports)
Made-with: Cursor
* docs(feishu): document cardkit:card:write for streaming
- README: permissions, upgrade note for existing apps, streaming toggle
- CHANNEL_PLUGIN_GUIDE: Feishu CardKit scope and when to disable streaming
Made-with: Cursor
* docs: address PR 2382 review (test path, plugin guide, README, English docstrings)
- Move Feishu streaming tests to tests/channels/
- Remove Feishu CardKit scope from CHANNEL_PLUGIN_GUIDE (plugin-dev doc only)
- README Feishu permissions: consistent English
- feishu.py: replace Chinese in streaming docstrings/comments
Made-with: Cursor