HeartbeatService was refactored from free-text HEARTBEAT_OK token
matching to a structured two-phase design (LLM tool call for
skip/run decision, then execution). The tests still used the old
on_heartbeat callback constructor and HEARTBEAT_OK_TOKEN import.
- Remove obsolete test_heartbeat_ok_detection test
- Update test_start_is_idempotent to use new provider+model constructor
- Add tests for _decide() skip path, trigger_now() run/skip paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The slackify_markdown library (markdown-it) fails to convert **bold** when
the closing ** is immediately followed by non-space text (e.g. **Status:**OK).
This is a very common LLM output pattern that results in raw ** showing up
in Slack messages.
Add _fixup_mrkdwn() post-processor that:
- Converts leftover **bold** → *bold* (Slack mrkdwn)
- Converts leftover ## headers → *bold* (safety net)
- Fixes over-escaped & in bare URLs
- Protects code fences and inline code from being mangled
Co-authored-by: Cursor <cursoragent@cursor.com>
The slackify_markdown library misses several patterns that LLMs commonly
produce, causing raw Markdown symbols (**bold**, ##headers) to appear
in Slack messages.
Add _fixup_mrkdwn() post-processor that:
- Converts leftover **bold** patterns (e.g. **Status:**OK where closing
** is adjacent to non-space chars)
- Fixes & over-escaping in bare URLs
- Protects code blocks from false-positive fixups
Co-authored-by: Cursor <cursoragent@cursor.com>
- Defer Brave API key resolution to execute() time instead of __init__,
so env var or config changes take effect without gateway restart
- Improve error message to reference actual config path
(tools.web.search.apiKey) instead of only mentioning env var
Fixes#1069 (issues 1 and 2 of 3)
Previously, `autoReplyEnabled=false` would block ALL email sends,
including proactive emails triggered from other channels (e.g., asking
nanobot on Feishu to send an email).
Now `autoReplyEnabled` only controls automatic replies to incoming
emails, not proactive sends. This allows users to disable auto-replies
while still being able to ask nanobot to send emails on demand.
Changes:
- Check if recipient is in `_last_subject_by_chat` to determine if
it's a reply
- Only skip sending when it's a reply AND auto_reply_enabled is false
- Add test for proactive send with auto_reply_enabled=false
- Update existing test to verify reply behavior
_sanitize_messages strips all non-standard keys from messages, including
reasoning_content. Thinking-enabled models like Moonshot Kimi k2.5
require reasoning_content to be present in assistant tool call messages
when thinking mode is on, causing a BadRequestError (#1014).
Add reasoning_content to _ALLOWED_MSG_KEYS so it passes through
sanitization when present.
Fixes#1014
Previously, WebSearchTool cached the API key in __init__, so keys added
to config.json or env vars after gateway startup were never picked up.
This caused a confusing 'BRAVE_API_KEY not configured' error even after
the key was correctly set (issue #1069).
Changes:
- Store the init-time key separately, resolve via property at each call
- Improve error message to guide users toward the correct fix
Closes#1069
Always provide an explicit httpx client to prevent MCP HTTP transport from inheriting httpx's default 5-second timeout, thereby avoiding conflicts with the upper layer tool's timeout settings.
Fixes#1042. When the LLM returns tool call arguments as a dict or
JSON string instead of parsed values, memory consolidation would fail
with "TypeError: data must be str, not dict".
Changes:
- Add type guard in MemoryStore.consolidate() to parse string arguments
and reject unexpected types gracefully
- Add regression tests covering dict args, string args, and edge cases