maintainer edit: compare exact-file allowlist entries using logical paths and require the resolved target to stay at that canonical path, so symlinks or junctions cannot redirect canonical memory files to an external write target.
Maintainer edit: keep capability-specific allowed_dir boundaries active even when the outer workspace scope is full access, and cover Dream plus ordinary full-access filesystem behavior.
maintainer edit: Dream write tools used file paths as directory roots, so a missing canonical memory file could be treated as a parent directory. Add exact-file allowlist support and keep skills/ as the only Dream write directory.
Treat the current live user message as the replay boundary for normal user turns, while keeping user-turn extension for history and consolidation paths that need it. Add regression coverage for the user-triggered long tool-turn case.
chengyongru reviewed #4367 and identified that the cloud branch
created a bare httpx.AsyncClient that lacked the SDK's default settings
(follow_redirects, connection pool limits). Since the SDK's
DefaultAsyncHttpxClient already has trust_env=True and proper defaults,
the simplest fix is to let http_client stay None for cloud endpoints.
Also updated the test to match the new behavior (http_client is None).
When the host has HTTP_PROXY / HTTPS_PROXY / ALL_PROXY set, httpx routes
all traffic through the proxy — including requests to localhost or LAN
addresses that the proxy typically cannot reach. This breaks local model
servers (Ollama, llama.cpp, vLLM) silently.
- Local endpoints: pass transport=httpx.AsyncHTTPTransport(proxy=None)
so proxy env vars are ignored for local traffic.
- Cloud endpoints: pass trust_env=True so corporate/VPN proxies work
without explicit configuration.
Fixes#4366
The shell safety guard in _guard_command() checked extracted absolute
paths only against cwd_path (the command's working directory). When
cwd was a subdirectory like ~/.nanobot/workspace/obsidian_notes, any
absolute path referencing the broader workspace (e.g. a sibling
directory or the workspace root itself) was incorrectly blocked with
"path outside working dir".
Fix: pass the workspace_root from _prepare_command() into
_guard_command() and check absolute paths against it as a fallback
when they are outside cwd_path. Paths truly outside the workspace
root are still blocked.
Added tests:
- test_exec_allows_workspace_paths_from_subdirectory
- test_exec_blocks_outside_paths_from_subdirectory
Feishu CardKit content updates can fail without raising, leaving a blank Generating card while the final streamed response skips normal send. Reopen streaming mode and retry once, close blank cards when the first update fails, and fall back to a regular card when final updates still fail.
Maintainer edit: make token truncation include the suffix within the budget and route the consolidator through the shared helper so recent-history and archive truncation keep the same semantics.
The recent-history section injected into the system prompt was capped by character count (_MAX_HISTORY_CHARS = 32_000). Characters are a poor proxy for tokens: ~32k chars of English is ~8k tokens, but the same char count of CJK text or code can be far more, so the cap could let the section blow well past its intended size on non English/code-heavy histories.
Add a reusable truncate_text_to_tokens() helper (reusing the tiktoken
cl100k_base encoder already used elsewhere, with a char-based fallback) and
switch the digest cap to a token budget (_MAX_HISTORY_TOKENS = 8_000),
matching the previous English-text size while holding regardless of
content.
The non-streaming retry called process_direct again with the same
content, persisting a duplicate user turn. Pass persist_user_message=False
so the retry recovers a response without re-recording the user message.
* fix(api): forward real LLM usage in /v1/chat/completions response
_chat_completion_response() hardcoded prompt_tokens/completion_tokens
to zero. Now reads agent_loop._last_usage (set by process_direct
after every LLM call) and forwards the actual prompt/completion counts.
Streaming path is unchanged; usage is only surfaced in non-streaming
responses for now.
Fixes#4309
* fix: use defensive getattr for _last_usage and add it to all test mock agents
- Use getattr(agent_loop, '_last_usage', None) in server.py for safety
- Add _last_usage = {} to mock agents in test_api_attachment.py and test_api_stream.py
- Prevents AttributeError/500 when mock agents don't have the attribute
* fix(api): preserve provider total usage
---------
Co-authored-by: michaelxer <michaelxer@users.noreply.github.com>
Co-authored-by: Xubin Ren <52506698+Re-bin@users.noreply.github.com>
Maintainer edit: subagents rebuilt their scoped ToolsConfig without carrying tools.file, which re-enabled built-in file tools after the parent agent disabled them. Preserve the file config and add loader/subagent coverage for the disabled path.
Avoid resubmitting unchanged schedules from the automation edit dialog so completed one-time automations can still have their content updated. Treat unchanged schedules as already-valid on the backend while preserving validation for actual schedule changes.
Maintainer edit: redact external channel chat identifiers from the WebUI automation payload and reject malformed or unschedulable automation updates before they mutate cron jobs.
The temperature suppression was hardcoded to only match opus-4-7. Newer
Anthropic models (opus-4-8, fable) also reject the parameter with a 400.
Normalize model_name to lowercase before matching so mixed-case configs
do not fall through. Add tests for opus-4-8 and fable across adaptive,
enabled, and no-thinking paths, plus a negative test confirming ordinary
models still send temperature.
Fixes#4333