Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
438f291
refactor(whatsapp): move bridge_url to config, remove separate creden…
vanducng Apr 6, 2026
c83c51b
fix(whatsapp): prevent typing indicator goroutine leak on consecutive…
vanducng Apr 6, 2026
f736750
feat(whatsapp): add media download + QR code auth + bridge status han…
vanducng Apr 6, 2026
24cdc24
fix(whatsapp): use ${1} syntax in HTML-to-WA regex replacements
vanducng Apr 6, 2026
eadf8e5
feat(whatsapp): add outbound media support and fix inbound media hand…
vanducng Apr 6, 2026
e7626ed
fix(whatsapp): fix shared volume mounts for media exchange
vanducng Apr 6, 2026
b4b5442
fix(whatsapp): fix group mention detection with LID + JID dual-identity
vanducng Apr 6, 2026
677760f
refactor(whatsapp): replace Node.js Baileys bridge with native whatsmeow
vanducng Apr 6, 2026
531bd7d
feat(whatsapp): show "GoClaw" as device name in Linked Devices
vanducng Apr 6, 2026
7b28f23
fix(whatsapp): wait for channel registration before QR start
vanducng Apr 6, 2026
3626820
fix(whatsapp): handle LID addressing for group messages
vanducng Apr 6, 2026
e3ca663
fix(whatsapp): correct comments — personal account, not bot
vanducng Apr 6, 2026
cf8f55a
feat(whatsapp): include quoted message context in replies
vanducng Apr 6, 2026
f705cd1
feat(whatsapp): add group pending history for message context
vanducng Apr 6, 2026
192f36a
feat(whatsapp): add message chunking and configurable history limit
vanducng Apr 6, 2026
1c8b846
fix(whatsapp): correct doctor/channels configured check
vanducng Apr 6, 2026
dac6e29
feat(whatsapp): render markdown tables as ASCII in code blocks
vanducng Apr 6, 2026
bb863d3
Revert "feat(whatsapp): render markdown tables as ASCII in code blocks"
vanducng Apr 6, 2026
7762cee
fix(whatsapp): cleanup bridge references and move DeviceProps to init
vanducng Apr 6, 2026
1dfbcf3
fix(whatsapp): address review feedback — media logs, mention tests, c…
vanducng Apr 6, 2026
e80a5ae
docs(whatsapp): remove library references, use generic direct connect…
vanducng Apr 6, 2026
5ec9b22
refactor(whatsapp): split 980-line whatsapp.go and fix review issues
viettranx Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/channels_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func channelsListCmd() *cobra.Command {
{"discord", cfg.Channels.Discord.Enabled, cfg.Channels.Discord.Token != ""},
{"zalo", cfg.Channels.Zalo.Enabled, cfg.Channels.Zalo.Token != ""},
{"feishu", cfg.Channels.Feishu.Enabled, cfg.Channels.Feishu.AppID != ""},
{"whatsapp", cfg.Channels.WhatsApp.Enabled, cfg.Channels.WhatsApp.BridgeURL != ""},
{"whatsapp", cfg.Channels.WhatsApp.Enabled, cfg.Channels.WhatsApp.Enabled},
}

if jsonOutput {
Expand Down
2 changes: 1 addition & 1 deletion cmd/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func runDoctor() {
checkChannel("Discord", cfg.Channels.Discord.Enabled, cfg.Channels.Discord.Token != "")
checkChannel("Zalo", cfg.Channels.Zalo.Enabled, cfg.Channels.Zalo.Token != "")
checkChannel("Feishu", cfg.Channels.Feishu.Enabled, cfg.Channels.Feishu.AppID != "")
checkChannel("WhatsApp", cfg.Channels.WhatsApp.Enabled, cfg.Channels.WhatsApp.BridgeURL != "")
checkChannel("WhatsApp", cfg.Channels.WhatsApp.Enabled, cfg.Channels.WhatsApp.Enabled)
}

// External tools
Expand Down
2 changes: 1 addition & 1 deletion cmd/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ func runGateway() {
instanceLoader.RegisterFactory(channels.TypeFeishu, feishu.FactoryWithPendingStore(pgStores.PendingMessages))
instanceLoader.RegisterFactory(channels.TypeZaloOA, zalo.Factory)
instanceLoader.RegisterFactory(channels.TypeZaloPersonal, zalopersonal.FactoryWithPendingStore(pgStores.PendingMessages))
instanceLoader.RegisterFactory(channels.TypeWhatsApp, whatsapp.Factory)
instanceLoader.RegisterFactory(channels.TypeWhatsApp, whatsapp.FactoryWithDB(pgStores.DB, pgStores.PendingMessages, "pgx"))
instanceLoader.RegisterFactory(channels.TypeSlack, slackchannel.FactoryWithPendingStore(pgStores.PendingMessages))
if err := instanceLoader.LoadAll(context.Background()); err != nil {
slog.Error("failed to load channel instances from DB", "error", err)
Expand Down
10 changes: 7 additions & 3 deletions cmd/gateway_channels_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ func registerConfigChannels(cfg *config.Config, channelMgr *channels.Manager, ms
}

if cfg.Channels.WhatsApp.Enabled {
if cfg.Channels.WhatsApp.BridgeURL == "" {
recordMissingConfig(channels.TypeWhatsApp, "Set channels.whatsapp.bridge_url in config.")
} else if wa, err := whatsapp.New(cfg.Channels.WhatsApp, msgBus, nil); err != nil {
waDialect := "pgx"
if strings.Contains(fmt.Sprintf("%T", pgStores.DB.Driver()), "sqlite") {
waDialect = "sqlite3"
}
wa, err := whatsapp.New(cfg.Channels.WhatsApp, msgBus, pgStores.Pairing, pgStores.DB, pgStores.PendingMessages, waDialect)
if err != nil {
channelMgr.RecordFailure(channels.TypeWhatsApp, "", err)
slog.Error("failed to initialize whatsapp channel", "error", err)
} else {
Expand Down Expand Up @@ -143,6 +146,7 @@ func wireChannelRPCMethods(server *gateway.Server, pgStores *store.Stores, chann
methods.NewChannelInstancesMethods(pgStores.ChannelInstances, msgBus, msgBus).Register(server.Router())
zalomethods.NewQRMethods(pgStores.ChannelInstances, msgBus).Register(server.Router())
zalomethods.NewContactsMethods(pgStores.ChannelInstances).Register(server.Router())
whatsapp.NewQRMethods(pgStores.ChannelInstances, channelMgr).Register(server.Router())
}

// Register agent links WS RPC methods
Expand Down
26 changes: 16 additions & 10 deletions docs/05-channels-messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ flowchart TD

| Feature | Telegram | Feishu/Lark | Discord | Slack | WhatsApp | Zalo OA | Zalo Personal |
|---------|----------|-------------|---------|-------|----------|---------|---------------|
| Connection | Long polling | WS (default) / Webhook | Gateway events | Socket Mode | External WS bridge | Long polling | Internal protocol |
| Connection | Long polling | WS (default) / Webhook | Gateway events | Socket Mode | Direct protocol (in-process) | Long polling | Internal protocol |
| DM support | Yes | Yes | Yes | Yes | Yes | Yes (DM only) | Yes |
| Group support | Yes (mention gating) | Yes | Yes | Yes (mention gating + thread cache) | Yes | No | Yes |
| Forum/Topics | Yes (per-topic config) | Yes (topic session mode) | -- | -- | -- | -- | -- |
| Message limit | 4,096 chars | Configurable (default 4,000) | 2,000 chars | 4,000 chars | N/A (bridge) | 2,000 chars | 2,000 chars |
| Message limit | 4,096 chars | Configurable (default 4,000) | 2,000 chars | 4,000 chars | WhatsApp native limit | 2,000 chars | 2,000 chars |
| Streaming | Typing indicator | Streaming message cards | Edit "Thinking..." | Edit "Thinking..." (throttled 1s) | No | No | No |
| Media | Photos, voice, files | Images, files (30 MB) | Files, embeds | Files (download w/ SSRF protection) | JSON messages | Images (5 MB) | -- |
| Media | Photos, voice, files | Images, files (30 MB) | Files, embeds | Files (download w/ SSRF protection) | Images, audio, video, documents | Images (5 MB) | -- |
| Speech-to-text | Yes (STT proxy) | -- | -- | -- | -- | -- | -- |
| Voice routing | Yes (VoiceAgentID) | -- | -- | -- | -- | -- | -- |
| Rich formatting | Markdown → HTML | Card messages | Markdown | Markdown → mrkdwn | Plain text | Plain text | Plain text |
Expand Down Expand Up @@ -430,15 +430,18 @@ Auto-enables when both bot_token and app_token are set.

## 9. WhatsApp

The WhatsApp channel communicates through an external WebSocket bridge (e.g., whatsapp-web.js based). GoClaw does not implement the WhatsApp protocol directly.
The WhatsApp channel connects directly to the WhatsApp network via the multi-device protocol. Authentication state is stored in the database (PostgreSQL standard, SQLite for desktop edition).

### Key Behaviors

- **Bridge connection**: Connects to configurable `bridge_url` via WebSocket
- **JSON format**: Messages sent/received as JSON objects
- **Auto-reconnect**: Exponential backoff (1s → 30s max)
- **DM and group support**: Group detection via `@g.us` suffix in chat ID
- **Media handling**: Array of file paths from bridge protocol
- **Direct connection**: In-process WhatsApp client (direct to WhatsApp servers, no external bridge)
- **Database auth store**: Persists auth state, keys, and device info in the database
- **QR code authentication**: Interactive QR code for initial pairing, served via WebSocket API
- **Auto-reconnect**: Built-in reconnection with exponential backoff
- **DM and group support**: Full group messaging with mention detection via JID format
- **Media handling**: Direct media download/upload to WhatsApp servers with type detection
- **Typing indicators**: Typing state managed per chat with auto-refresh
- **Group mention gating**: Detects when bot is mentioned via LID (Local ID) and JID (standard format)

---

Expand Down Expand Up @@ -590,7 +593,10 @@ flowchart TD
| `internal/channels/slack/format.go` | Markdown → Slack mrkdwn pipeline |
| `internal/channels/slack/reactions.go` | Status emoji reactions on messages |
| `internal/channels/slack/stream.go` | Streaming message updates via placeholder editing |
| `internal/channels/whatsapp/whatsapp.go` | WhatsApp: external WS bridge |
| `internal/channels/whatsapp/whatsapp.go` | WhatsApp: direct protocol client, QR auth, database persistence |
| `internal/channels/whatsapp/factory.go` | Channel factory, database dialect detection |
| `internal/channels/whatsapp/qr_methods.go` | QR code generation and authentication flow |
| `internal/channels/whatsapp/format.go` | Message formatting (HTML-to-WhatsApp) |
| `internal/channels/zalo/zalo.go` | Zalo OA: Bot API, long polling |
| `internal/channels/zalo/personal/channel.go` | Zalo Personal: reverse-engineered protocol |
| `internal/store/pg/pairing.go` | Pairing: code generation, approval, persistence (database-backed) |
Expand Down
15 changes: 15 additions & 0 deletions docs/17-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ All notable changes to GoClaw Gateway are documented here. Format follows [Keep

### Added

#### WhatsApp Native Protocol Integration (2026-04-06)
- **Direct protocol migration**: Replaced Node.js Baileys bridge with direct in-process WhatsApp connectivity
- **Database auth persistence**: Auth state, device keys, and client metadata stored in PostgreSQL (standard) or SQLite (desktop)
- **QR authentication**: Interactive QR code authentication for device linking without external bridge relay
- **No more bridge_url**: Removed `bridge_url` configuration, eliminated `docker-compose.whatsapp.yml`, removed `bridge/whatsapp/` sidecar service
- **Enhanced media handling**: Direct media download/upload to WhatsApp servers with automatic type detection and streaming
- **Improved mention detection**: Group mention detection now uses LID (Local ID) + JID (standard format) for robust message routing
- **Files added**:
- `internal/channels/whatsapp/factory.go` — Dialect detection and channel factory
- `internal/channels/whatsapp/qr_methods.go` — QR code generation and authentication flow
- `internal/channels/whatsapp/format.go` — HTML-to-WhatsApp message formatting
- Database-backed auth persistence for cross-platform support

### Refactored

#### Parallel Sub-Agent Enhancement (#600) (2026-03-31)
- **Smart leader delegation**: Conditional leader delegation prompt instead of forced delegation for all subagent spawns
- **Compaction prompt persistence**: Preserves pending subagent and team task state across context summarization to maintain work continuity
Expand Down
25 changes: 17 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ require (
github.com/mattn/go-shellwords v1.0.12
github.com/mymmrac/telego v1.6.0
github.com/redis/go-redis/v9 v9.18.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/slack-go/slack v0.19.0
github.com/spf13/cobra v1.10.2
github.com/titanous/json5 v1.0.0
github.com/wailsapp/wails/v2 v2.11.0
github.com/zalando/go-keyring v0.2.8
go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0
Expand Down Expand Up @@ -53,6 +55,7 @@ require (
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beeper/argo-go v1.1.2 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
Expand All @@ -64,11 +67,12 @@ require (
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/creachadair/msync v0.7.1 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gaissmai/bart v0.18.0 // indirect
Expand All @@ -91,7 +95,7 @@ require (
github.com/leaanthony/u v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
Expand All @@ -101,11 +105,13 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/spf13/cast v1.7.1 // indirect
Expand All @@ -117,17 +123,20 @@ require (
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
github.com/wailsapp/go-webview2 v1.0.22 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.mau.fi/libsignal v0.2.1 // indirect
go.mau.fi/util v0.9.6 // indirect
go.uber.org/atomic v1.11.0 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down Expand Up @@ -175,14 +184,14 @@ require (
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.49.0
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
golang.org/x/net v0.50.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.33.0
golang.org/x/text v0.34.0
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
google.golang.org/protobuf v1.36.11
)
Loading
Loading