- Fix assertion in streaming dict fallback test (trailing space in data
not reflected in expected value).
- Add two regression tests proving that models with reasoning_content
(e.g. DeepSeek-R1) and standard models (no reasoning fields) are
completely unaffected by the reasoning fallback.
Made-with: Cursor
Add comprehensive tests for the StepFun Plan API compatibility fix:
- _parse dict branch: content and reasoning_content fallback to reasoning
- _parse SDK object branch: same fallback for pydantic response objects
- _parse_chunks dict branch: reasoning field handled in streaming mode
- _parse_chunks SDK branch: reasoning fallback for SDK delta objects
- Precedence tests: reasoning_content field takes priority over reasoning
Refs: fix(provider): support StepFun Plan API reasoning field fallback
- Propagate `description` from MCP prompt arguments into the JSON
Schema so LLMs can better understand prompt parameters.
- Align generic-exception error message with tool/resource wrappers
(drop redundant `{exc}` detail).
- Extend test fixture to mock `mcp.shared.exceptions.McpError`.
- Add tests for argument description forwarding and McpError handling.
Made-with: Cursor
Add MCPResourceWrapper and MCPPromptWrapper classes that expose MCP
server resources and prompts as nanobot tools. Resources are read-only
tools that fetch content by URI, and prompts are read-only tools that
return filled prompt templates with optional arguments.
- MCPResourceWrapper: reads resource content (text and binary) via URI
- MCPPromptWrapper: gets prompt templates with typed arguments
- Both handle timeouts, cancellation, and MCP SDK 1.x error types
- Resources and prompts are registered during server connection
- Gracefully handles servers that don't support resources/prompts
- 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
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
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
- 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
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
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.
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
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.
- 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.
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
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.
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