feat(channels): add Signal channel via signal-cli HTTP daemon#63
feat(channels): add Signal channel via signal-cli HTTP daemon#63DonPrus merged 23 commits intonullclaw:mainfrom
Conversation
|
Let me know quarter you think @DonPrus, I’m thinking to change the config option to match telegram and option names in Openclaw, so instead of allowed_users -> allowed_from, etc. |
|
Also, I’m not familiar with the codebase yet but if the model implemented this in the most efficient way ( currently) there may be some work to be done in the channel abstraction layer as there are way too many changes needed in files outside the channel folder. For example, onboarding, status, the main loop - all should be automatically propagated after a channel is added (vs the current hardcoded approach). |
|
Ty @DonPrus, fantastic changes |
I’m not sure about this, if I am to judge this by the openclaw config conventions group_allow_from are user accounts (E164 numbers) which are allowed to talk within configured/permitted groups, the original code (perhaps changes now) uses Judging by the chosen name I believe you want to match this behavior to openclaw, in which case we need to modify the code. I’m thinking it’s still valuable to decide which groups the agent is listening to at all and on top to have group_allow_from which is the “allowlist” of groups only. |
|
9812db5, FYI, the DebugAllocator was segfaulting due to a race condition on debug build when running the daemon. |
Implement Signal messaging channel that connects to a signal-cli daemon running in HTTP mode (signal-cli --http <port>). Follows the vtable-based channel pattern established by existing channels. Architecture: - SSE event stream for receiving messages at /api/v1/events - JSON-RPC for sending messages/typing indicators at /api/v1/rpc - curl subprocess for all HTTP (consistent with other channels) - Manual JSON construction via json_util helpers Features: - Full envelope processing (data messages, group messages) - Allowlist-based access control for users and groups (empty allowlist = deny all, secure by default) - Story message filtering (ignore_stories, default true) - Attachment placeholder support (ignore_attachments, default true) - Wildcard (*) support in allowlists - UUID normalization (uuid: prefix) in allowlists - E.164 phone number validation - Trailing slash stripping from base URL - Message splitting at 4096 char limit - Best-effort typing indicators - Health check via /api/v1/check endpoint Config (accounts wrapper pattern): channels.signal.accounts.<id>.http_url (required) channels.signal.accounts.<id>.account (required, E.164 phone) channels.signal.accounts.<id>.allowed_users channels.signal.accounts.<id>.allowed_groups channels.signal.accounts.<id>.ignore_attachments (default: true) channels.signal.accounts.<id>.ignore_stories (default: true) Files: - src/channels/signal.zig: channel implementation + tests - src/config_types.zig: SignalConfig struct + ChannelsConfig field - src/config_parse.zig: JSON config parsing for Signal channel - src/channels/root.zig: sub-module registration
…dd agent routing - Fix story message parsing panic in Signal (else-if guard on .object access) - Add Signal to has_channel check in doctor.zig - Use msg.deinit() instead of manual field-by-field free in Signal loop - Unify config naming: allowed_users→allow_from, allowed_groups→group_allow_from, allowed_senders→allow_from, allowlist→allow_from/group_allow_from across all channels - Add allow_from field to Line and OneBot configs - Register Email, Line, QQ, OneBot, MaixCam in central ChannelsConfig with JSON parsing - Add 5 new channels to status, onboard, doctor, and main channel listings - Create agent_routing.zig: 7-tier OpenClaw-compatible binding resolution (29 tests) - Fix WhatsApp to use case-insensitive phone matching (isAllowed vs isAllowedExact) - Fix Matrix vtableSend to respect target room parameter
Expands native image parsing to Discord, WhatsApp, and Signal by extracting attachments and substituting them with [IMAGE:path] tag for multimodal agent workflows.
…l safety, access control - Eliminate duplicate config structs in email/line/qq/onebot/maixcam (import from config_types.zig) - Make LineConfig access_token/channel_secret required, add allow_from to QQConfig - Add missing channels to status.zig, doctor.zig, daemon.zig, main.zig (channel doctor/add) - Signal SSE parser: add JSON tag checks before .object dereference (crash fix) - Signal: log errors instead of silently masking as empty poll - Signal: accumulate multiline SSE data: events correctly - Signal: group_allow_from now checks sender in groups (matching OpenClaw), not group ID - Signal: session keys include group context to prevent cross-chat leaks - Signal: change ignore_attachments/ignore_stories defaults to false (match references) - Signal: implement SIGNAL_HTTP_URL/SIGNAL_ACCOUNT env overrides - Enforce allow_from in OneBot (handleEvent) and Line (parseAndFilterEvents) - Discord: add initFromConfig() to pass allow_from/mention_only/intents - channel start: accept explicit channel name arg, default Telegram-first
- Implement granular group policies (allowlist, policy string) for Telegram, WhatsApp, and iMessage. - Align Discord configuration keys (mention_only -> require_mention) with parent spec. - Fix Signal group message validation bypass. - Resolve config parsing test memory leaks and failing upstream stream chunks.
…iring - Add ChannelManager (src/channel_manager.zig) — central orchestrator for all channel lifecycle (init, start, supervise, stop), replacing ~250 lines of hardcoded Telegram/Signal logic in daemon.zig - JSON safety guards across 5 channel files (line, onebot, qq, whatsapp, signal) to prevent crashes on malformed webhook payloads - Signal error propagation: SSE poll failures now bubble up instead of being silently swallowed; curlGetSSE gains --fail flag for HTTP error detection - QQ allow_from: parse config + enforce allowlist in handleMessageCreate - compatible.zig: preserve multimodal content_parts when merging system messages - Gateway expansion: add /line and /lark webhook routes with signature verification and event parsing - CLI expansion: `channel start` now supports discord, qq, onebot in addition to telegram and signal - Agent routing: wire resolveRoute into Telegram/Signal polling loops; add agent_bindings field to Config - Fix websocket.zig for Zig 0.15 Io.Reader API (readVec instead of read) - Multi-account warning in config parser - Extract serializeContentPart helper to avoid duplication
…stener parity - Add agent routing resolution to all channel loops (Telegram, Discord, Signal, WhatsApp, etc.) - Expand ChannelManager with multi-account support, listener wiring, per-channel routing - Gateway: add channel-level routing, session management, health endpoints - Config: parse accounts map, agent routing bindings, channel-specific configs - Signal: group peer ID extraction, attachment fetch improvements - All channels: consistent session key resolution through agent routing
The daemon command was crashing in Debug mode due to a thread-safety issue when loading config inside the gateway thread. The fix involves two changes: 1. Always use smp_allocator instead of DebugAllocator in main(). DebugAllocator is not thread-safe and causes memory corruption when used across threads. 2. Pass the already-loaded config from daemon.run() to gateway.run() instead of having gateway.run() load its own config in a separate thread. This ensures config is loaded in the main thread where it's safe, and avoids redundant config loading. The gateway.run() function now accepts an optional config_ptr parameter for backward compatibility with direct invocations. Fixes: Debug mode segfault at config_parse.zig:95 in parseJson
…us wiring Phase 1 — structural fixes: media param on Channel.send vtable (all 16 channels), dedup channel instances between ChannelManager and channel_loop, gateway-loop supervision, typing indicators and error replies for bus channels, session eviction for bus channels. Phase 2 — session config and routing: SessionConfig with DmScope and IdentityLink types, buildSessionKeyWithScope (4 DM scope modes), normalizeId for agent/account IDs, resolveLinkedPeerId, main_session_key on ResolvedRoute, thread session keys. Phase 3 — multi-account support: ChannelsConfig fields changed from optional to slices for 7 channel types (telegram, discord, slack, signal, qq, onebot, maixcam), Primary() helpers, getAllAccounts with alphabetical sort, per-account Entry.account_id in ChannelManager, ~90 callers updated. Phase 4 — webhook to bus wiring: event_bus on GatewayState, gateway.run accepts optional bus, webhook endpoints (telegram, whatsapp, line, lark) publish to bus in daemon mode, standalone in-request mode preserved. 30 files changed, +897/-279, 3155 tests passing.
…mat parity
- Fix routing bug: bindings with peer+guild now require ALL constraints
to match (allConstraintsMatch), not just the tier-specific field
- Fix gateway.zig compile error on Linux (|*cfg| → |cfg|)
- Add camelCase + dash-format support for session config parsing
(dmScope, idleMinutes, identityLinks, per-peer/per-channel-peer)
- Add map-format identity_links parsing ({"alice": ["telegram:111"]})
- 34 new agent_routing tests: route priority, dmScope, identity links,
session key continuity, normalizeId, thread keys
- 18 new config tests: multi-account edge cases, session config parsing
with both snake_case and camelCase formats
On Windows, std.net.Stream.Handle is ws2_32.SOCKET (opaque pointer), not i32. Use std.net.Stream.Handle alias and INVALID_SOCKET sentinel for cross-platform socket storage. Close via closesocket() on Windows.
|
@ibhagwan, thanks, I will double check every thing tomorrow and than I will be ready for merge |
- Add channel_catalog.zig: centralized channel metadata, DRY doctor/onboard/status/main - Fix Codex provider: always include `instructions` field (API requires it) - Add 19 new tests: discord/qq bus routing, telegram/line/lark session keys, signal/qq/onebot/maixcam multi-account, single-account selection, channel manager wiring - Refactor channel listing to use catalog loop instead of per-channel conditionals
… safety checks - Refactor collectConfiguredChannels to comptime-iterate ChannelsConfig fields - Add initFromConfig() to all channel types for uniform construction - Add account_id to OutboundMessage and ChannelRegistry entries - Add resolveRouteWithSession() for dm_scope-aware routing - Add ListenerMode to channel_catalog, requiresRuntime(), findById() - Deduplicate polling sources (same bot_token/http_url+account) - Add setBus() to DiscordChannel, safety checks for root_val != .object - Refactor config_parse.zig, simplify channel parsing - Update README: 3,230+ tests, 17 channels
|
@DonPrus, when building debug mode with simply Per my understanding it’s because ReleaseSmall doesn’t panic for unreachable but the behavior will be undefined and can lead to some nasty voodoo bugs. I don’t have any other channels configured, can you check with another channel (or signal if you have) and lmk if you get the panic too in a debug build? I’ve been working on it for a while now suspecting the memory is being corrupted somewhere else causing the vtable call to panic. |
|
@ibhagwan, I added some tests, but couldnt reproduce it now, could you try it again? |
Pulled latest changes, still crashing on the first signal message in debug mode, it's very consistent for me but the source of the memory corruption is ellusive, I'm still debugging it will keep you posted. Have you tried with signal channel or other channels only? I'm suspecting something in the newly added signal channel code. |
|
Found the issue @DonPrus, it was only happening on memory.backend=markdown due to workspace_dir ref being corrupted, testing and will commit the fix in a sec. |
MarkdownMemory stored a borrowed slice to workspace_dir, but that slice pointed to a temporary string allocated in initChannelRuntime. After initChannelRuntime returned, the temporary was freed, but MarkdownMemory continued using the dangling pointer. When that freed memory was reused for the session key, workspace_dir appeared corrupted with session data. The crash manifested as 'panic: reached unreachable code' when implStore tried to use the corrupted path containing the session key. Fix: - MarkdownMemory.init now duplicates the workspace_dir string - MarkdownMemory.deinit now frees the duplicated string This ensures the workspace_dir remains valid for the lifetime of the MarkdownMemory instance.
|
Looks good for me, lets wait checks and I will merge it. |
Amazign work tysm @DonPrus! |
…gistry) (CodeRabbit #63) Move the builtin_tools cleanup errdefer ABOVE the allocator.create call so that if the registry allocation OOMs, all tool structs from allTools() are properly freed instead of leaked. Co-Authored-By: mccoy <hbzgzr@gmail.com>
Implement Signal messaging channel that connects to a signal-cli daemon running in HTTP mode (signal-cli --http ). Follows the vtable-based channel pattern established by existing channels.
Architecture:
Features:
Config (accounts wrapper pattern):
channels.signal.accounts..http_url (required)
channels.signal.accounts..account (required, E.164 phone)
channels.signal.accounts..allowed_users
channels.signal.accounts..allowed_groups
channels.signal.accounts..ignore_attachments (default: true)
channels.signal.accounts..ignore_stories (default: true)
Files:
Closes #58