386 Commits

Author SHA1 Message Date
xzq.xu
586d4e2411 fix(feishu): preserve tool hints in final card content
Tool hints should be kept as permanent content in the streaming card
so users can see which tools were called (matching the standalone card
behavior). Previously, hints were stripped when new deltas arrived or
when the stream ended, causing tool call information to disappear.

Now:
- New delta: hint becomes permanent content, delta appends after it
- New tool hint: replaces the previous hint (unchanged)
- Resuming/stream_end: hint is preserved in the final text

Updated 3 tests to verify hint preservation semantics.

Made-with: Cursor
2026-04-08 21:02:21 +08:00
xzq.xu
a4bb1923ac fix(feishu): prevent tool hint stacking and clean hints on stream_end
Three fixes for inline tool hints:

1. Consecutive tool hints now replace the previous one instead of
   stacking — the old suffix is stripped before appending the new one.

2. When _resuming flushes the buffer, any trailing tool hint suffix
   is removed so it doesn't persist into the next streaming segment.

3. When final _stream_end closes the card, tool hint suffix is
   cleaned from the text before the final card update.

Adds 3 regression tests covering all three scenarios.

Made-with: Cursor
2026-04-08 21:02:21 +08:00
xzq.xu
8d6f41e484 feat(feishu): streaming resuming + inline tool hints
Two improvements to Feishu streaming card experience:

1. Handle _resuming in send_delta: when a mid-turn _stream_end arrives
   with resuming=True (tool call between segments), flush current text
   to the card but keep the buffer alive so subsequent segments append
   to the same card instead of creating a new one.

2. Inline tool hints into streaming cards: when a tool hint arrives
   while a streaming card is active, append it to the card content
   (e.g. "🔧 web_fetch(...)") instead of sending a separate card.
   The hint is automatically stripped when the next delta arrives.

Made-with: Cursor
2026-04-08 21:02:21 +08:00
chengyongru
4962867112 fix(tool-hint): fold paths in exec commands instead of blind truncation
exec tool hints previously used val[:40] which cut paths mid-segment
(e.g. "D:\Documents\GitHub\nanobot.worktree…"). Now uses regex to
detect file paths in commands and abbreviates them properly, with
smart truncation at chain separators (&&, |, ;) as fallback.
2026-04-08 11:37:17 +08:00
chengyongru
c44d4f2b2b fix(test): fix two flaky tests on Windows
- test_exec_head_tail_truncation: use temp script file instead of
  python -c to avoid cmd.exe quote-parsing issues after PR #2893
- test_grep_files_with_matches_supports_head_limit_and_offset: query
  full result set first to avoid mtime-dependent sort assumption
2026-04-07 21:30:45 +08:00
Xubin Ren
ae27d69ecb fix(exec): add Windows support for shell command execution
ExecTool hardcoded bash, breaking exec on Windows. Now uses cmd.exe
via COMSPEC on Windows with a curated minimal env (PATH, SYSTEMROOT,
etc.) that excludes secrets. bwrap sandbox gracefully skips on Windows.
2026-04-07 21:13:32 +08:00
chengyongru
ba38d41ad1 Merge remote-tracking branch 'origin/main' into nightly 2026-04-07 20:49:53 +08:00
Xubin Ren
82dec12f66 refactor: extract tool hint formatting to utils/tool_hints.py
- Move _tool_hint implementation from loop.py to nanobot/utils/tool_hints.py
- Keep thin delegation in AgentLoop._tool_hint for backward compat
- Update test imports to test format_tool_hints directly

Made-with: Cursor
2026-04-07 15:15:07 +08:00
chengyongru
3e3a7654f8 fix(agent): address code review findings for tool hint enhancement
- C1: Fix IndexError on empty list arguments via _get_args() helper
- I1: Remove redundant branch in _fmt_known
- I2: Export abbreviate_path from nanobot.utils.__init__
- I3: Fix _abbreviate_url negative-budget format consistency
- S1: Move FORMATS to class-level _TOOL_HINT_FORMATS constant
- S2: Add list_dir to FORMATS registry (ls path)
- G1-G5: Add tests for empty list args, None args, URL edge cases,
  mixed folding groups, and list_dir format
2026-04-07 15:15:07 +08:00
chengyongru
b1d3c00deb test(feishu): add compatibility tests for new tool hint format 2026-04-07 15:15:07 +08:00
chengyongru
238a9303d0 test: update tool_hint assertion to match new format 2026-04-07 15:15:07 +08:00
chengyongru
8ca9960077 feat(agent): rewrite _tool_hint with registry, path abbreviation, and call folding 2026-04-07 15:15:07 +08:00
chengyongru
f452af6c62 feat(utils): add abbreviate_path for smart path/URL truncation 2026-04-07 15:15:07 +08:00
Xubin Ren
02597c3ec9 fix(runner): silent retry on empty response before finalization 2026-04-07 15:03:41 +08:00
Xubin Ren
0355f20919 test: add regression tests for _resolve_mentions
7 tests covering: single mention, dual IDs, no-id skip, multiple mentions,
no mentions, empty text, and key-not-in-text edge case.

Made-with: Cursor
2026-04-07 14:03:55 +08:00
Xubin Ren
075bdd5c3c refactor: move SafeFileHistory to module level + add regression tests
- Promote _SafeFileHistory to module-level SafeFileHistory for testability
- Add 5 regression tests: surrogates, normal text, emoji, mixed CJK, multi-surrogates

Made-with: Cursor
2026-04-07 13:57:34 +08:00
04cb
f4904c4bdf fix(cron): add optional name parameter to separate job label from message (#2680) 2026-04-07 13:22:20 +08:00
Xubin Ren
dad9c07843 fix(tests): update Tavily usage tests to match actual API response shape
The _parse_tavily_usage implementation was updated to use the real
{account: {plan_usage, plan_limit, ...}} structure, but the tests
still used the old flat {used, limit, breakdown} format.

Made-with: Cursor
2026-04-06 19:17:55 +08:00
Xubin Ren
35f53a721d refactor: consolidate _parse_retry_after_headers into base class
Merge the three retry-after header parsers (base, OpenAI, Anthropic)
into a single _extract_retry_after_from_headers on LLMProvider that
handles retry-after-ms, case-insensitive lookup, and HTTP date.

Remove the per-provider _parse_retry_after_headers duplicates and
their now-unused email.utils / time imports. Add test for retry-after-ms.

Made-with: Cursor
2026-04-06 08:44:52 +00:00
Xubin Ren
aeba9a23e6 refactor: remove dead _error_response wrapper in Anthropic provider
Fold _error_response back into _handle_error to match OpenAI/Azure
convention. Update all call sites and tests accordingly.

Made-with: Cursor
2026-04-06 08:35:02 +00:00
Xubin Ren
b575aed20e Merge origin/main into fix/structured-retry-classification-main
Made-with: Cursor
2026-04-06 08:28:20 +00:00
Xubin Ren
ebf29d87ae fix: include byteplus providers, guard None reasoning_effort, merge extra_body
- Add byteplus and byteplus_coding_plan to thinking param providers
- Only send extra_body when reasoning_effort is explicitly set
- Use setdefault().update() to avoid clobbering existing extra_body
- Add 7 regression tests for thinking params

Made-with: Cursor
2026-04-06 16:12:08 +08:00
chengyongru
3723cd726e fix(msteams): remove optional deps from dev extras and gate tests
PyJWT and cryptography are optional msteams deps; they should not be
bundled into the generic dev install.  Tests now skip the entire file
when the deps are missing, following the dingtalk pattern.
2026-04-06 15:48:10 +08:00
Bob Johnson
8f0b653a4c Fix MSTeams PR review follow-ups 2026-04-06 15:48:10 +08:00
T3chC0wb0y
5857f7fdd0 Add Microsoft Teams channel on current nightly base 2026-04-06 15:48:10 +08:00
Ben Lenarts
d0527a8cf4 feat(email): add attachment extraction support
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.
2026-04-06 15:09:44 +08:00
Xubin Ren
bdec2637ae test: add regression test for oversized stream-end splitting
Made-with: Cursor
2026-04-06 06:39:23 +00:00
Xubin Ren
c9d4b7b905 Merge remote-tracking branch 'origin/main' into pr-2449
Made-with: Cursor

# Conflicts:
#	nanobot/utils/evaluator.py
2026-04-06 06:30:11 +00:00
Xubin Ren
897d5a7e58 test: add regression tests for JID suffix classification and LID cache
Made-with: Cursor
2026-04-06 06:19:06 +00:00
Xubin Ren
35dde8a30e refactor: unify voice transcription config across all channels
- Move transcriptionProvider to global channels config (not per-channel)
- ChannelManager auto-resolves API key from matching provider config
- BaseChannel gets transcription_provider attribute, no more getattr hack
- Remove redundant transcription fields from WhatsAppConfig
- Update README: document transcriptionProvider, update provider table

Made-with: Cursor
2026-04-06 06:07:30 +00:00
Xubin Ren
7b7a3e5748 fix: media_paths NameError, import order, add error logging and tests
- Move media_paths assignment before voice message handling to prevent
  NameError at runtime
- Fix broken import layout in transcription.py (httpx/loguru after class)
- Add error logging to OpenAITranscriptionProvider matching Groq style
- Add regression tests for voice transcription and no-media fallback

Made-with: Cursor
2026-04-06 06:01:14 +00:00
Xubin Ren
c88d97c652 fix: fall back to heuristic when bot open_id fetch fails
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
2026-04-06 13:49:38 +08:00
Ben Lenarts
202938ae73 feat: support ${VAR} env var interpolation in config secrets
Allow config.json to reference environment variables via ${VAR_NAME}
syntax. Variables are resolved at runtime by resolve_config_env_vars(),
keeping the raw templates in the Pydantic model so save_config()
preserves them. This lets secrets live in a separate env file
(e.g. loaded by systemd EnvironmentFile=) instead of plain text
in config.json.
2026-04-06 13:43:26 +08:00
Xubin Ren
7ffd93f48d refactor: move search_usage to utils/searchusage, remove brave stub
- Rename agent/tools/search_usage.py → utils/searchusage.py
  (not an LLM tool, matches utils/ naming convention)
- Remove redundant _fetch_brave_usage — handled by else branch
- Move test to tests/utils/test_searchusage.py

Made-with: Cursor
2026-04-06 13:37:55 +08:00
whs
bc0ff7f214 feat(status): add web search provider usage to /status command 2026-04-06 13:37:55 +08:00
Xubin Ren
28e0a76b80 fix: path_append must not clobber login shell PATH
Seeding PATH in the env before bash -l caused /etc/profile
to skip its default PATH setup, breaking standard commands.
Move path_append to an inline export so the login shell
establishes a proper base PATH first.

Add regression test: ls still works when path_append is set.

Made-with: Cursor
2026-04-06 13:20:53 +08:00
Ben Lenarts
be6063a142 security: prevent exec tool from leaking process env vars to LLM
The exec tool previously passed the full parent process environment to
child processes, which meant LLM-generated commands could access secrets
stored in env vars (e.g. API keys from EnvironmentFile=).

Switch from subprocess_shell with inherited env to bash login shell
with a minimal environment (HOME, LANG, TERM only). The login shell
sources the user's profile for PATH setup, making the pathAppend
config option a fallback rather than the primary PATH mechanism.
2026-04-06 13:20:53 +08:00
Xubin Ren
9f96be6e9b fix(sandbox): mount media directory read-only inside bwrap sandbox 2026-04-05 19:08:38 +00:00
Xubin Ren
cef0f3f988 refactor: replace podman-seccomp.json with minimal cap_add, harden bwrap, add sandbox tests 2026-04-05 19:03:06 +00:00
Jack Lu
bcb8352235 refactor(agent): streamline hook method calls and enhance error logging
- Introduced a helper method `_for_each_hook_safe` to reduce code duplication in hook method implementations.
- Updated error logging to include the method name for better traceability.
- Improved the `SkillsLoader` class by adding a new method `_skill_entries_from_dir` to simplify skill listing logic.
- Enhanced skill loading and filtering logic, ensuring workspace skills take precedence over built-in ones.
- Added comprehensive tests for `SkillsLoader` to validate functionality and edge cases.
2026-04-06 02:51:10 +08:00
Xubin Ren
bb9da29eff test: add regression tests for private DM thread session key derivation
Made-with: Cursor
2026-04-06 02:44:21 +08:00
Ilya Semenov
0d6bc7fc11 fix(telegram): support threads in DMs 2026-04-06 02:44:21 +08:00
Xubin Ren
4b4d8b506d test: add regression test for DuckDuckGo asyncio.wait_for timeout guard
Made-with: Cursor
2026-04-06 02:21:51 +08:00
Xubin Ren
90caf5ce51 test: remove duplicate test_jina_422_falls_back_to_duckduckgo
The same test function name appeared twice; Python silently shadows the
first definition so it never ran.  Keep the version that also asserts
the request URL contains "s.jina.ai".

Made-with: Cursor
2026-04-06 02:06:00 +08:00
KimGLee
f422de8084 fix(web-search): fix Jina search format and fallback 2026-04-06 02:06:00 +08:00
chengyongru
401d1f57fa fix(dream): allow LLM to retry on tool errors instead of failing immediately
Dream Phase 2 uses fail_on_tool_error=True, which terminates the entire
run on the first tool error (e.g. old_text not found in edit_file).
Normal agent runs default to False so the LLM can self-correct and retry.
Dream should behave the same way.
2026-04-05 22:10:34 +08:00
chengyongru
7d2c62716c fix(dream): allow LLM to retry on tool errors instead of failing immediately
Dream Phase 2 uses fail_on_tool_error=True, which terminates the entire
run on the first tool error (e.g. old_text not found in edit_file).
Normal agent runs default to False so the LLM can self-correct and retry.
Dream should behave the same way.
2026-04-05 22:09:42 +08:00
chengyongru
3003cb8465 test(feishu): add unit tests for reaction add/remove and auto-cleanup 2026-04-05 16:53:05 +08:00
Jiajun Xie
f86f226c17 fix(cli): prevent spinner ANSI escape codes from being printed verbatim
Fixes #2591

The "nanobot is thinking..." spinner was printing ANSI escape codes
literally in some terminals, causing garbled output like:
  ?[2K?[32m⠧?[0m ?[2mnanobot is thinking...?[0m

Root causes:
1. Console created without force_terminal=True, so Rich couldn't
   reliably detect terminal capabilities
2. Spinner continued running during user input prompt, conflicting
   with prompt_toolkit

Changes:
- Set force_terminal=True in _make_console() for proper ANSI handling
- Add stop_for_input() method to StreamRenderer
- Call stop_for_input() before reading user input in interactive mode
- Add tests for the new functionality
2026-04-05 16:50:49 +08:00
Xubin Ren
33bef8d508 Merge remote-tracking branch 'origin/main' into feat/search-tools
Made-with: Cursor
2026-04-04 14:37:59 +00:00