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
- Enhance _strip_think to handle stray tags:
* Remove unmatched closing tags (</think>)
* Remove incomplete blocks (<think> ... to end of string)
- Apply _strip_think to tool hint messages as well
- Prevents blank/parse errors from showing </think> in chat outputs
Fixes issue with empty </think> appearing in Feishu tool call cards and other messages.
- Extract card creation logic into _send_tool_hint_card() helper
- Improves code organization and testability
- Update tests to use pytest.mark.asyncio for cleaner async testing
- Remove redundant asyncio.run() calls in favor of native async test functions
- Format multiple tool calls each on their own line
- Change title from 'Tool Call' to 'Tool Calls' (plural)
- Add explicit 'text' language for code block
- Improves readability and supports displaying longer content
- Update tests to match new formatting
- Tool hint messages with _tool_hint metadata now render as formatted code blocks
- Uses Feishu interactive card message type with markdown code fences
- Shows "Tool Call" header followed by code in a monospace block
- Adds comprehensive unit tests for the new functionality
Co-Authorship-Bot: Claude Opus 4.6 <noreply@anthropic.com>
- Add special handling for tool hint messages (_tool_hint metadata)
- Send tool calls using Feishu's "code" message type with formatting
- Tool calls now appear as formatted code snippets in Feishu chat
- Add unit tests for the new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement asynchronous memory consolidation that runs in the background when
sessions are idle, instead of blocking user interactions after each message.
Changes:
- MemoryConsolidator: Add background task management with idle detection
* Track session activity timestamps
* Background loop checks idle sessions every 30s
* Consolidation triggers only when session idle > 60s
- AgentLoop: Integrate background task lifecycle
* Start consolidation task when loop starts
* Stop gracefully on shutdown
* Record activity on each message
- Refactor maybe_consolidate_by_tokens: Keep sync API but schedule async
- Add debug logging for consolidation completion
Benefits:
- Non-blocking: Users no longer wait for consolidation after responses
- Efficient: Only consolidate idle sessions, avoiding redundant work
- Scalable: Background task can process multiple sessions efficiently
- Backward compatible: Existing API unchanged
Tests: 11 new tests covering background task lifecycle, idle detection,
scheduling, and error handling. All passing.
🤖 Generated with Claude Code
- Require both history_entry and memory_update, reject null/empty values
- Fallback to tool_choice=auto when provider rejects forced function call
- After 3 consecutive consolidation failures, raw-archive messages to
HISTORY.md without LLM summarization to prevent context window overflow
Some providers (e.g. Dashscope in thinking mode) reject object-style
tool_choice with "does not support being set to required or object".
Retry once with tool_choice="auto" instead of failing silently.
Made-with: Cursor
On Windows, sys.argv[0] may be just "nanobot" without full path when
running from PATH. os.execv() doesn't search PATH, causing restart to
fail with "No such file or directory".
Fix by using `python -m nanobot` instead of relying on sys.argv[0].
Fixes#1937