The DM allowlist check split sender_id on '|' and looked for raw membership
in the allow_from list. Senders carry their phone number with a leading
'+' but admins routinely write allowlist entries without it (or vice
versa), and UUID/ACI matches were case-sensitive. Both forms now flow
through _normalize_signal_id, so an entry like 19995550001 matches a
sender +19995550001 and a UUID matches case-insensitively.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Hoist the cell-strip patterns to module level so they match the rest of
the module's regex style and aren't reparsed on every call.
- Type the markdown transform callback and the mention id walker so the
inline Callable signature is no longer an untyped Any.
- Add _HTTP_TIMEOUT_SECONDS alongside the other class-level tunables.
- Reject group_message_buffer_size <= 0 in a Pydantic field_validator
rather than silently disabling the buffer at write time.
- Mark SignalConfig.allow_from as a computed_field so it shows up in
model_dump() instead of being invisible to serialization.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
split_message can break a long Signal payload into multiple JSON-RPC sends,
but the previous code attached the full textStyle list only to chunk 0.
Style ranges in later chunks were dropped, and ranges whose offsets pointed
past chunk 0's end were sent as invalid metadata against chunk 0.
Add _partition_styles, which rebases each range against the chunk it lives
in (in UTF-16 code units, matching the markdown converter) and splits
boundary-spanning ranges across the chunks they touch. Whitespace trimmed
by split_message's lstrip is skipped so offsets stay aligned.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signal's BodyRange (via signal-cli's textStyle) interprets start/length as
UTF-16 code units, but the Phase-3 assembly used Python's len(), which counts
code points. A single non-BMP character (e.g. an emoji) earlier in a message
shifted every subsequent styled span left by one unit, dropping the last
letter of bold/italic words.
Track a running UTF-16 offset in the assembly loop and add regression tests
covering emojis, supplementary CJK, ZWJ sequences, and a multi-section
message that mirrors the reported failure.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Integrates signal-cli daemon via HTTP JSON-RPC as a nanobot channel.
Supports DMs and group chats with open/allowlist access policies,
markdown→Signal text style conversion, typing indicators, attachment
handling, group message context buffering, and automatic reconnect
with exponential backoff.
Includes unit tests for channel lifecycle, message routing, mention
detection, markdown conversion, and message splitting.
Originally based on https://github.com/HKUDS/nanobot/pull/601.
Link nanobot.wiki documentation in 10 languages from README header:
English, 简体中文, 繁體中文, Español, Français, Bahasa Indonesia,
日本語, 한국어, Русский, Tiếng Việt.
Add APIFree as a built-in OpenAI-compatible provider. APIFree offers
agent-optimised models such as skywork-ai/skyclaw-v1 through an
OpenAI-compatible API at https://api.apifree.ai/agent/v1.
Changes:
- Register apifree provider in the provider registry
- Add config schema field
- Add documentation with configuration example
- Add provider tests, websocket channel tests, and webui tests
- Add provider icon in settings UI
Channel lazy load: discover_enabled() only imports enabled channel
modules instead of all 18 modules with heavy SDKs (telegram, discord,
slack, etc). discover_all() now delegates to discover_enabled().
Lazy OpenAI client: defer AsyncOpenAI() + httpx construction to
_ensure_client() with asyncio.Lock double-checked locking. openai
and httpx imports moved from module-level into _ensure_client().
Minor: lazy Nanobot/RunResult and CronService exports via __getattr__.
Benchmark: 6910ms → 460ms (-93.3%)
- Remove generated_image_paths_from_messages() and _extract_text_payload() from artifacts.py (no runtime callers)
- Remove session_attachments.py entirely (merge_turn_media_into_last_assistant and stage_media_paths_for_session_replay had no runtime callers)
- Remove test_session_media_persist.py and the orphaned test in test_artifacts.py
The runtime media-attachment mechanism was broken for streaming channels
(e.g. WebSocket): the _streamed flag caused _send_once to skip the final
OutboundMessage that carried generated media, so images were never delivered.
Rather than adding complex coordination between streaming and media delivery,
delegate image delivery to the LLM: after generate_image returns artifact
paths, the next_step prompt now instructs the LLM to call the message tool
with the paths in the media parameter. This works uniformly across all
channels, streaming or not.
Remove generated_media from TurnContext, _assemble_outbound, and _state_save.
Update prompts in identity.md, SKILL.md, message tool description, and
artifacts.py to reflect the new flow.
Updates docs/image-generation.md and skills/image-generation/SKILL.md to
include MiniMax configuration examples, supported aspect ratios, and
troubleshooting references. Also updates the supported provider list to
include minimax alongside openrouter, aihubmix, and gemini.
Adds ImageGenerationProvider ABC with shared __init__, _http_post(), and
_require_images(). Introduces _IMAGE_GEN_PROVIDERS registry with
register/get/image_gen_provider_configs() helpers.
Four existing providers (OpenRouter, AIHubMix, Gemini, MiniMax) now inherit
from the base class and self-register. Adding a new provider only requires
writing one class + one registration line.
Eliminates if/else chains in the tool dispatch and hardcoded provider config
dicts in commands.py (3 sites) and nanobot.py (1 site). Fixes the agent CLI
command missing image_generation_provider_configs entirely.
Also simplifies test monkeypatch targets to patch the registry lookup.
Adds GeminiImageGenerationClient covering both Imagen 4 (:predict) and
Gemini Flash (:generateContent), wires the gemini ProviderConfig through
the SDK, API server, and gateway entry points, and updates the
image-generation docs and skill. Errors from the Gemini endpoints are
logged and surface with the HTTP status and parsed message instead of an
empty string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add MiniMaxImageGenerationClient with support for:
- Text-to-image generation via MiniMax image-01 model
- Reference image support (subject_reference)
- Aspect ratio selection
- Proper error handling aligned with existing providers
Wire up MiniMax provider config in ImageGenerationTool, gateway,
serve, and Nanobot class.
Match the frontend fix: tool_trace_lines_from_events now processes end and error phases with call_id deduplication so transcript replay shows tool calls correctly.
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Tool call events only displayed at phase=start, but progress_hook sends end/error phases after agent execution. Accept all three phases with call_id deduplication to prevent duplicate rendering.
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Extract the [M] Model Presets interactive CRUD screen from PR #3696
and adapt it to the current main branch schema (fallback_models
instead of fallback_presets). Adds preset cache, field handlers for
model_preset/provider/fallback_models, and 9 new tests.
Add remark-breaks plugin so that single newlines in assistant messages
(such as /help output) render as line breaks instead of being collapsed
into a single paragraph by standard markdown behavior.