Instead of adding a separate load_skill tool to bypass workspace restrictions,
extend ReadFileTool with extra_allowed_dirs so it can read builtin skill paths
while keeping write/edit tools locked to the workspace. Fixes the original issue
for both main agent and subagents.
Made-with: Cursor
When restrictToWorkspace is enabled, the agent cannot read builtin skill
files via read_file since they live outside the workspace. This adds a
dedicated load_skill tool that reads skills by name through the SkillsLoader,
which accesses files directly via Python without the workspace restriction.
- Add LoadSkillTool to filesystem tools
- Register it in the agent loop
- Update system prompt to instruct agent to use load_skill instead of read_file
- Remove raw filesystem paths from skills summary
Replace the static provider-level supports_vision check with a
reactive fallback: when a model returns an image-unsupported error,
strip image_url blocks from messages and retry once. This avoids
maintaining an inaccurate vision capability table and correctly
handles gateway/unknown model scenarios.
Also extract _safe_chat() to deduplicate try/except boilerplate
in chat_with_retry().
- Add field to ProviderSpec (default True)
- Add and methods in LiteLLMProvider
- Filter image_url content blocks in before sending to non-vision models
- Reverts session-layer filtering from original PR (wrong layer)
This fixes the issue where switching from Claude (vision-capable) to
non-vision models (e.g., Baidu Qianfan) causes API errors due to
unsupported image_url content blocks.
The provider layer is the correct place for this filtering because:
1. It has access to model/provider capabilities
2. It only affects non-vision models
3. It preserves session layer purity (storage should not know about model capabilities)
With custom_llm_provider kwarg handling routing, the openrouter/ prefix
caused model names like anthropic/claude-sonnet-4-6 to become
openrouter/anthropic/claude-sonnet-4-6, which OpenRouter API rejects.
- Add nanobot/utils/evaluator.py: lightweight LLM tool-call to decide notify/silent after background task execution
- Remove magic token injection from heartbeat and cron prompts
- Clean session history (no more <SILENT_OK> pollution)
- Add tests for evaluator and updated heartbeat three-phase flow
Appends a strict instruction to background task prompts (cron and heartbeat)
directing the agent to return a `<SILENT_OK>` token if there is nothing
material to report. Adds conditional logic to intercept this token and
suppress the outbound message to the user, preventing notification spam
from autonomous background checks.
Appends a strict instruction to background task prompts (cron and heartbeat)
directing the agent to return a `<SILENT_OK>` token if there is nothing
material to report. Adds conditional logic to intercept this token and
suppress the outbound message to the user, preventing notification spam
from autonomous background checks.
Replace platform-specific shell=True logic with shutil.which('npm') to
resolve the full path to the npm executable. This is cleaner because:
- No shell=True needed (safer, no shell injection risk)
- No platform-specific branching (sys.platform checks removed)
- Works identically on Windows, macOS, and Linux
- shutil.which() resolves npm.cmd on Windows automatically
The npm path check that already existed in _get_bridge_dir() is now
reused as the resolved path for subprocess calls. The same pattern is
applied to channels_login().
On Windows, npm is installed as npm.cmd (a batch script), not a direct
executable. When subprocess.run() is called with a list like
['npm', 'install'] without shell=True, Python's CreateProcess cannot
locate npm.cmd, resulting in:
FileNotFoundError: [WinError 2] The system cannot find the file specified
This fix adds a sys.platform == 'win32' check before each npm subprocess
call. On Windows, it uses shell=True with a string command so the shell
can resolve npm.cmd. On other platforms, the original list-based call is
preserved unchanged.
Affected locations:
- _get_bridge_dir(): npm install, npm run build
- channels_login(): npm start
No behavioral change on Linux/macOS.
Merge the latest main branch into the Telegram media filename fix and keep the file_unique_id-based download path on top of the refactored media handling and newer Telegram tests.
Made-with: Cursor
- test_channel_plugins: fix assertion logic for discoverable channels
- test_filesystem_tools: normalize path separators for Windows
- test_tool_validation: use python to generate output, avoid cmd line limits
- Add `reply_to_message: bool = False` config to `FeishuConfig`
- Parse `parent_id` and `root_id` from incoming events into metadata
- Fetch quoted message content via `im.v1.message.get` and prepend
`[Reply to: ...]` context for the LLM when a user quotes a message
- Add `_reply_message_sync` using `im.v1.message.reply` API so the
bot's response appears as a threaded quote in Feishu
- First outbound message uses reply API; subsequent chunks fall back
to `create` to avoid duplicate quote bubbles; progress messages
always use `create`
- Add 19 unit tests covering all new code paths