* feat(dream): enhance memory cleanup with staleness detection
- Phase 1: add [FILE-REMOVE] directive and staleness patterns (14-day
threshold, completed tasks, superseded info, resolved tracking)
- Phase 2: add explicit cleanup rules, file paths section, and deletion
guidance to prevent LLM path confusion
- Inject current date and file sizes into Phase 1 context for age-aware
analysis
- Add _dream_debug() helper for observability (dream-debug.log in workspace)
- Log Phase 1 analysis output and Phase 2 tool events for debugging
Tested with glm-5-turbo: MEMORY.md reduced from 149 to 108-129 lines
across two rounds, correctly identifying and removing weather data,
detailed incident info, completed research, and stale discussions.
* refactor(dream): replace _dream_debug file logger with loguru
Remove the custom _dream_debug() helper that wrote to dream-debug.log
and use the existing loguru logger instead. Phase 1 analysis is logged
at debug level, tool events at info level — consistent with the rest
of the codebase and no extra log file to manage.
* fix(dream): make stale scan independent of conversation history
Reframe Phase 1 from a single comparison task to two independent
tasks: history diff AND proactive stale scan. The LLM was skipping
stale content that wasn't referenced in conversation history (e.g.
old triage snapshots). Now explicitly requires scanning memory files
for staleness patterns on every run.
* fix(dream): correct old_text param name and truncate debug log
- Phase 2 prompt: old_string -> old_text to match EditFileTool interface
- Phase 1 debug log: truncate analysis to 500 chars to avoid oversized lines
* refactor(dream): streamline prompts by separating concerns
Phase 1 owns all staleness judgment logic; Phase 2 is pure execution
guidance. Remove duplicated cleanup rules from Phase 2 since Phase 1
already determines what to add/remove. Fix remaining old_string -> old_text.
Total prompt size reduced ~45% (870 -> 480 tokens).
* fix(dream): add FILE-REMOVE execution guidance to Phase 2 prompt
Phase 2 was only processing [FILE] additions and ignoring [FILE-REMOVE]
deletions after the cleanup rules were removed. Add explicit mapping:
[FILE] → add content, [FILE-REMOVE] → delete content.
- 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 lark-oapi client requires token types to be explicitly configured
so that the SDK can obtain and attach the tenant_access_token to raw
requests. Without this, `_fetch_bot_open_id()` would fail with
"Missing access token for authorization" because the token had not
been provisioned at the time of the call.
On Windows, certain Unicode input (emoji, mixed-script text, surrogate
pairs) causes prompt_toolkit's FileHistory to crash with
UnicodeEncodeError when writing the history file.
Fix: wrap FileHistory with a _SafeFileHistory subclass that sanitizes
surrogate characters before writing, replacing invalid sequences instead
of crashing.
Fixes#2846
Add CI step to detect unused imports (F401) and unused variables (F841)
with ruff. Clean up existing violations:
- Remove unused Consolidator import in agent/__init__.py
- Remove unused re import in agent/loop.py
- Remove unused Path import in channels/feishu.py
- Remove unused ContentRepositoryConfigError import in channels/matrix.py
- Remove unused field and CommandHandler imports in channels/telegram.py
- Remove unused exception variable in channels/weixin.py
len(content) counts Unicode code points, not UTF-8 bytes. For non-ASCII
content such as Chinese or emoji, the reported count would be lower than
the actual bytes written to disk, which is misleading to the agent.
The pydantic to_camel function generates 'e2EeEnabled' (treating 'ee'
as a word boundary) for the field 'e2ee_enabled'. Users writing
'e2eeEnabled' in their config get the default value instead.
Fix: add explicit alias='e2eeEnabled' to override the incorrect
auto-generated alias. Both 'e2eeEnabled' and 'e2ee_enabled' now work.
Fixes#2851
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
The Tavily /usage endpoint returns a nested "account" object with
plan_usage/plan_limit/search_usage/etc fields, not the flat structure
with used/limit/breakdown that was assumed. This caused all usage
values to be None.
The /status command tried to access web search config via
`loop.config.tools.web.search`, but AgentLoop has no `config` attribute.
This caused the search usage lookup to silently return None, so web
search provider usage was never displayed.
Fix: use `loop.web_config.search` which is the actual attribute
set during AgentLoop.__init__.
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 nanobot-api service (OpenAI-compatible HTTP API on port 8900)
- Uses isolated workspace (/root/.nanobot/api-workspace) to avoid
session/memory conflicts with nanobot-gateway
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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.