1434 Commits

Author SHA1 Message Date
Flo
41843b0fb0
fix: clear heartbeat session to prevent token overflow (#2398) 2026-03-23 22:58:41 +08:00
Jesse
528b3cfe5a
feat: configurable context budget for tool-loop iterations (#2317)
* feat: add contextBudgetTokens config field for tool-loop trimming

* feat: implement _trim_history_for_budget for tool-loop cost reduction

* feat: thread contextBudgetTokens into AgentLoop constructor

* feat: wire context budget trimming into agent loop

* refactor: move trim_history_for_budget to helpers and add docs

- Extract trim_history_for_budget() as a pure function in helpers.py
- AgentLoop._trim_history_for_budget becomes a thin wrapper
- Add docs/CONTEXT_BUDGET.md with usage guide and trade-off notes
- Replace wrapper tests with direct helper unit tests

---------

Co-authored-by: chengyongru <chengyongru.ai@gmail.com>
2026-03-23 18:13:03 +08:00
chengyongru
0182ce2852 fix(qq): handle file:// URI on Windows in _read_media_bytes
urlparse on Windows puts the path in netloc, not path. Use
(parsed.path or parsed.netloc) to get the correct raw path.
2026-03-23 15:18:54 +08:00
chengyongru
3a1a7ef269 Merge main into nightly
Resolve telegram.py conflict by keeping main's streaming implementation
(_StreamBuf + send_delta with edit_message_text approach) over nightly's
_send_with_streaming (draft-based approach).
2026-03-23 15:04:15 +08:00
chengyongru
4c58f29e8f refactor(channels): abstract login() into BaseChannel, unify CLI commands
Move channel-specific login logic from CLI into each channel class via a
new `login(force=False)` method on BaseChannel. The `channels login <name>`
command now dynamically loads the channel and calls its login() method.

- WeixinChannel.login(): calls existing _qr_login(), with force to clear saved token
- WhatsAppChannel.login(): sets up bridge and spawns npm process for QR login
- CLI no longer contains duplicate login logic per channel
- Update CHANNEL_PLUGIN_GUIDE to document the login() hook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:58:36 +08:00
ZhangYuanhan-AI
d7413bbe67 feat(weixin): add outbound media file sending via CDN upload
Previously the WeChat channel's send() method only handled text messages,
completely ignoring msg.media. When the agent called message(media=[...]),
the file was never delivered to the user.

Implement the full WeChat CDN upload protocol following the reference
@tencent-weixin/openclaw-weixin v1.0.2:
  1. Generate a client-side AES-128 key (16 random bytes)
  2. Call getuploadurl with file metadata + hex-encoded AES key
  3. AES-128-ECB encrypt the file and POST to CDN with filekey param
  4. Read x-encrypted-param from CDN response header as download param
  5. Send message with the media item (image/video/file) referencing
     the CDN upload

Also adds:
- _encrypt_aes_ecb() for AES-128-ECB encryption (reverse of existing
  _decrypt_aes_ecb)
- Media type detection from file extension (image/video/file)
- Graceful error handling: failed media sends notify the user via text
  without blocking subsequent text delivery

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:58:36 +08:00
ZhangYuanhan-AI
a255df24d4 fix(agent): instruct LLM to use message tool for file delivery
During testing, we discovered that when a user requests the agent to
send a file (e.g., "send me IMG_1115.png"), the agent would call
read_file to view the content and then reply with text claiming
"file sent" — but never actually deliver the file to the user.

Root cause: The system prompt stated "Reply directly with text for
conversations. Only use the 'message' tool to send to a specific
chat channel", which led the LLM to believe text replies were
sufficient for all responses, including file delivery.

Fix: Add an explicit IMPORTANT instruction in the system prompt
telling the LLM it MUST use the 'message' tool with the 'media'
parameter to send files, and that read_file only reads content
for its own analysis.

Co-Authored-By: qulllee <qullkui@tencent.com>
2026-03-23 13:58:36 +08:00
qulllee
803630ec63 feat: add media message support in agent context and message tool
Cherry-picked from PR #2355 (ad128a7) — only agent/context.py and agent/tools/message.py.

Co-Authored-By: qulllee <qullkui@tencent.com>
2026-03-23 13:58:36 +08:00
ZhangYuanhan-AI
001c6abce3 feat(weixin): add personal WeChat channel via ilinkai HTTP long-poll API
Add a new WeChat (微信) channel that connects to personal WeChat using
the ilinkai.weixin.qq.com HTTP long-poll API. Protocol reverse-engineered
from @tencent-weixin/openclaw-weixin v1.0.2.

Features:
- QR code login flow (nanobot weixin login)
- HTTP long-poll message receiving (getupdates)
- Text message sending with proper WeixinMessage format
- Media download with AES-128-ECB decryption (image/voice/file/video)
- Voice-to-text from WeChat + Groq Whisper fallback
- Quoted message (ref_msg) support
- Session expiry detection and auto-pause
- Server-suggested poll timeout adaptation
- Context token caching for replies
- Auto-discovery via channel registry

No WebSocket, no Node.js bridge, no local WeChat client needed — pure
HTTP with a bot token obtained via QR code scan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:58:36 +08:00
Xubin Ren
aba0b83a77 fix(memory): reserve completion headroom for consolidation
Trigger token consolidation before prompt usage reaches the full context window so response tokens and tokenizer estimation drift still fit safely within the model budget.

Made-with: Cursor
2026-03-23 11:54:44 +08:00
Xubin Ren
8f5c2d1a06 fix(cli): stop spinner after non-streaming interactive replies 2026-03-23 03:28:10 +00:00
chengyongru
a46803cbd7 docs(provider): add mistral intro 2026-03-23 11:07:46 +08:00
Desmond Sow
f64ae3b900 feat(provider): add OpenVINO Model Server provider (#2193)
add OpenVINO Model Server provider
2026-03-23 11:07:46 +08:00
Matt von Rohr
7878340031 feat(providers): add Mistral AI provider
Register Mistral as a first-class provider with LiteLLM routing,
MISTRAL_API_KEY env var, and https://api.mistral.ai/v1 default base.

Includes schema field, registry entry, and tests.
2026-03-23 11:07:46 +08:00
Xubin Ren
9d5e511a6e feat(streaming): centralize think-tag filtering and add Telegram streaming
- Add strip_think() to helpers.py as single source of truth
- Filter deltas in agent loop before dispatching to consumers
- Implement send_delta in TelegramChannel with progressive edit_message_text
- Remove duplicate think filtering from CLI stream.py and telegram.py
- Remove legacy fake streaming (send_message_draft) from Telegram
- Default Telegram streaming to true
- Update CHANNEL_PLUGIN_GUIDE.md with streaming documentation

Made-with: Cursor
2026-03-23 10:20:41 +08:00
Xubin Ren
f2e1cb3662 feat(cli): extract streaming renderer to stream.py with Rich Live
Move ThinkingSpinner and StreamRenderer into a dedicated module to keep
commands.py focused on orchestration. Uses Rich Live with manual refresh
(auto_refresh=False) and ellipsis overflow for stable streaming output.

Made-with: Cursor
2026-03-23 10:20:41 +08:00
Xubin Ren
bd621df57f feat: add streaming channel support with automatic fallback
Provider layer: add chat_stream / chat_stream_with_retry to all providers
(base fallback, litellm, custom, azure, codex). Refactor shared kwargs
building in each provider.

Channel layer: BaseChannel gains send_delta (no-op) and supports_streaming
(checks config + method override). ChannelManager routes _stream_delta /
_stream_end to send_delta, skips _streamed final messages.

AgentLoop._dispatch builds bus-backed on_stream/on_stream_end callbacks
when _wants_stream metadata is set. Non-streaming path unchanged.

CLI: clean up spinner ANSI workarounds, simplify commands.py flow.
Made-with: Cursor
2026-03-23 10:20:41 +08:00
Xubin Ren
e79b9f4a83 feat(agent): add streaming groundwork for future TUI
Preserve the provider and agent-loop streaming primitives plus the CLI experiment scaffolding so this work can be resumed later without blocking urgent bug fixes on main.

Made-with: Cursor
2026-03-23 10:20:41 +08:00
chengyongru
0537c417f6 fix(context): restore lost current_role parameter from PR #2104 conflict resolution
The build_messages() method was missing the current_role parameter that
loop.py calls with, causing a TypeError at runtime. This restores the
parameter with its default value of "user" to match the original PR #2104.
2026-03-23 09:56:39 +08:00
chengyongru
46d1a6448a fix(schema): restore lost changes from cherry-pick conflict resolution
- Restore enable attribute to ExecToolConfig
- Remove deprecated memory_window field (was removed in f44c4f9 but brought back by cherry-pick)
- Restore exclude=True on openai_codex and github_copilot oauth providers
2026-03-22 21:23:54 +08:00
kohath
9f433e366e feat(feishu): add thread reply support for topic group messages 2026-03-22 21:00:42 +08:00
guanka
4fff377855 Fix Flask port reuse error on wecom_app restart 2026-03-22 21:00:42 +08:00
xzq.xu
99d1cd5298 fix(cron): support tz parameter with at for one-time scheduled tasks
The tz parameter was previously only allowed with cron_expr. When users
specified tz with at for one-time tasks, it returned an error. Now tz
works with both cron_expr and at — naive ISO datetimes are interpreted
in the given timezone via ZoneInfo.

- Relax validation: allow tz with cron_expr or at
- Apply ZoneInfo to naive datetimes in the at branch
- Update SKILL.md with at+tz examples
- Add automated tests for tz+at combinations

Co-authored-by: weitongtong <tongtong.wei@nodeskai.com>
Made-with: Cursor
2026-03-22 21:00:42 +08:00
Jinxiang Gan
c4c0ac8eb2 Make multimodal input limits configurable 2026-03-22 20:45:56 +08:00
flobo3
37ca487e04 fix(agent): handle edge cases in tool hints path hiding 2026-03-22 20:45:31 +08:00
flobo3
76fa8790dc feat: hide absolute workspace paths in tool hints 2026-03-22 20:45:31 +08:00
xzq.xu
a2edee145f fix(loop): add return_exceptions=True to parallel tool gather
Without this flag, a BaseException (e.g. CancelledError from /stop)
in one tool would propagate immediately and discard results from the
other concurrent tools, corrupting the OpenAI message format.

With return_exceptions=True, all tool results are collected; any
exception is converted to an error string for the LLM.

Made-with: Cursor
2026-03-22 20:45:31 +08:00
xzq.xu
6028b4828b perf(loop): parallelize tool execution with asyncio.gather
Tool calls from a single LLM response are independent by design —
the model batches them precisely because they can run concurrently.
Replace the serial for-loop with asyncio.gather so N tools complete
in max(time_i) instead of sum(time_i).

Made-with: Cursor
2026-03-22 20:45:31 +08:00
chengyongru
e04a22a3cd docs(provider): add mistral intro 2026-03-22 20:45:31 +08:00
Desmond Sow
712a554dff feat(provider): add OpenVINO Model Server provider (#2193)
add OpenVINO Model Server provider
2026-03-22 20:45:31 +08:00
flobo3
8cc5c65ce6 feat(whatsapp): add group_policy to control bot response behavior in groups 2026-03-22 20:45:31 +08:00
guanka
00409c378a feat(channel): support wecom-app. (#2173)
Co-authored-by: guanka001 <guanka001@ke.com>
2026-03-22 20:45:31 +08:00
Flo
e8238d7ede feat(telegram): add silent_tool_hints config to disable notifications for tool hints (#2252) 2026-03-22 20:45:31 +08:00
Chen Junda
d076c5fd84 fix(qq): fix local file outbound and add svg as image type (#2294)
- Fix _read_media_bytes treating local paths as URLs: local file
  handling code was dead code placed after an early return inside the
  HTTP try/except block. Restructure to check for local paths (plain
  path or file:// URI) before URL validation, so files like
  /home/.../.nanobot/workspace/generated_image.svg can be read and
  sent correctly.
- Add .svg to _IMAGE_EXTS so SVG files are uploaded as file_type=1
  (image) instead of file_type=4 (file).
- Add tests for local path, file:// URI, and missing file cases.

Fixes: https://github.com/HKUDS/nanobot/pull/1667#issuecomment-4096400955

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 20:45:31 +08:00
Chen Junda
189460f267 feat(qq): bot can send and receive images and files (#1667)
Implement file upload and sending for QQ C2C messages

Reference: https://github.com/tencent-connect/botpy/blob/master/examples/demo_c2c_reply_file.py

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: chengyongru <chengyongru.ai@gmail.com>
2026-03-22 20:45:31 +08:00
Matt von Rohr
1ec5db9a36 feat(providers): add Mistral AI provider
Register Mistral as a first-class provider with LiteLLM routing,
MISTRAL_API_KEY env var, and https://api.mistral.ai/v1 default base.

Includes schema field, registry entry, and tests.
2026-03-22 20:45:20 +08:00
flobo3
b8a584430c feat(telegram): add react_emoji config for incoming messages 2026-03-22 20:45:20 +08:00
Xubin Ren
5fd66cae5c
Merge PR #1109: perf: optimize prompt cache hit rate for Anthropic models
perf: optimize prompt cache hit rate for Anthropic models
2026-03-22 14:23:41 +08:00
Xubin Ren
931cec3908 Merge remote-tracking branch 'origin/main' into pr-1109
Resolve conflict in context.py: keep main's build_messages which already
merges runtime context into user message (achieving the same cache goal).
The real value-add from this PR is the second cache breakpoint in
litellm_provider.py.

Made-with: Cursor
2026-03-22 06:14:18 +00:00
Xubin Ren
1c71489121 fix(agent): count all message fields in token estimation
estimate_prompt_tokens() only counted the `content` text field, completely
missing tool_calls JSON (~72% of actual payload), reasoning_content,
tool_call_id, name, and per-message framing overhead. This caused the
memory consolidator to never trigger for tool-heavy sessions (e.g. cron
jobs), leading to context window overflow errors from the LLM provider.

Also adds reasoning_content counting and proper per-message overhead to
estimate_message_tokens() for consistent boundary detection.

Made-with: Cursor
2026-03-22 12:19:44 +08:00
Xubin Ren
48c71bb61e refactor(agent): unify process_direct to return OutboundMessage
Merge process_direct() and process_direct_outbound() into a single
interface returning OutboundMessage | None. This eliminates the
dual-path detection logic in CLI single-message mode that relied on
inspect.iscoroutinefunction to distinguish between the two APIs.

Extract status rendering into a pure function build_status_content()
in utils/helpers.py, decoupling it from AgentLoop internals.

Made-with: Cursor
2026-03-22 00:39:38 +08:00
Xubin Ren
064ca256f5
Merge PR #1985: feat: add /status command to show runtime info
feat: add /status command to show runtime info
2026-03-22 00:11:34 +08:00
Xubin Ren
a8176ef2c6 fix(cli): keep direct-call rendering compatible in tests
Only use process_direct_outbound when the agent loop actually exposes it as an async method, and otherwise fall back to the legacy process_direct path. This keeps the new CLI render-metadata flow without breaking existing test doubles or older direct-call implementations.

Made-with: Cursor
2026-03-21 16:07:14 +00:00
Xubin Ren
e430b1daf5 fix(agent): refine status output and CLI rendering
Keep status output responsive while estimating current context from session history, dropping low-value queue/subagent counters, and marking command-style replies for plain-text rendering in CLI. Also route direct CLI calls through outbound metadata so help/status formatting stays explicit instead of relying on content heuristics.

Made-with: Cursor
2026-03-21 15:52:10 +00:00
Xubin Ren
4d1897609d fix(agent): make status command responsive and accurate
Handle /status at the run-loop level so it can return immediately while the agent is busy, and reset last-usage stats when providers omit usage data. Also keep Telegram help/menu coverage for /status without changing the existing final-response send path.

Made-with: Cursor
2026-03-21 15:21:32 +00:00
Xubin Ren
570ca47483 Merge branch 'main' into pr-1985 2026-03-21 09:48:09 +00:00
Xubin Ren
e87bb0a82d fix(mcp): preserve schema semantics during normalization
Only normalize nullable MCP tool schemas for OpenAI-compatible providers so optional params still work without collapsing unrelated unions. Also teach local validation to honor nullable flags and add regression coverage for nullable and non-nullable schemas.

Made-with: Cursor
2026-03-21 14:35:47 +08:00
haosenwang1018
b6cf7020ac fix: normalize MCP tool schema for OpenAI-compatible providers 2026-03-21 14:35:47 +08:00
Xubin Ren
9f10ce072f Merge PR #2304: feat(agent): implement native multimodal tool perception
Add native image content blocks for read_file and web_fetch, preserve the multimodal tool-result path through the agent loop, and keep session history compact with image placeholders. Also harden web_fetch against redirect-based SSRF bypasses and add regression coverage for image reads and blocked private redirects.
2026-03-21 05:39:17 +00:00
Xubin Ren
445a96ab55 fix(agent): harden multimodal tool result flow
Keep multimodal tool outputs on the native content-block path while
restoring redirect SSRF checks for web_fetch image responses. Also share
image block construction, simplify persisted history sanitization, and
add regression tests for image reads and blocked private redirects.

Made-with: Cursor
2026-03-21 05:34:56 +00:00