Metadata-Version: 2.4
Name: kestrel-channel-whatsapp
Version: 0.2.1
Summary: WhatsApp channel for Kestrel agents — isolated-venv feature (WhatsApp-Web via neonize + Twilio).
License-Expression: MIT
Requires-Python: <3.15,>=3.11
Requires-Dist: kestrel-sovereign-sdk<1,>=0.23.0
Provides-Extra: service
Requires-Dist: neonize<0.4,>=0.3.18; extra == 'service'
Requires-Dist: segno<2,>=1.6; extra == 'service'
Requires-Dist: twilio<10,>=9; extra == 'service'
Description-Content-Type: text/markdown

# kestrel-channel-whatsapp

WhatsApp messaging channel for [Kestrel](https://github.com/KestrelSovereignAI)
agents. It runs as an **isolated-venv feature**: the transport (neonize, which
needs protobuf 7 — incompatible with the host's gRPC/Google-AI stack) runs in a
per-agent venv and process, and the host talks to it over the SDK's stdio
JSON-RPC isolated-feature contract.

It's a **single package**. The base install is the lightweight host-side marker
(SDK only). The transport (neonize/twilio/segno) is the `[service]` extra, which
the host loader installs into a per-agent isolated venv — so the heavy,
protobuf-7 deps never touch the host venv:

```
host venv:           pip install kestrel-channel-whatsapp            # marker, SDK-only
per-agent venv:      pip install "kestrel-channel-whatsapp[service]"  # + transport (done by the loader)
```

## How it integrates

The service advertises a `whatsapp` **channel capability** at initialize. The
host (kestrel-sovereign ≥ 0.31, the isolated-runtime channel bridge from #1825)
registers a forwarding `ChannelAdapter` into `ChannelFeature.registry`, so the
agent uses the generic channels tools unchanged:

| Direction | Path |
| --- | --- |
| Outbound | `channels_send(channel="whatsapp", to, message)` → host proxy → service `whatsapp_send` tool → provider |
| Inbound | provider → service emits a `channel.inbound` event → host proxy → `ChannelFeature.handle_inbound` → `channel.message` cognition signal → agent replies via `channels_send` |

Service tools: `whatsapp_send`, `whatsapp_status`, `whatsapp_link` (QR pairing
for the web provider).

## Providers

| Provider | Outbound | Inbound | Notes |
| --- | --- | --- | --- |
| `web` (default) | ✅ | ✅ | [neonize](https://github.com/krypton-byte/neonize) (whatsmeow) — links as a companion device. Scan the `whatsapp_link` QR in WhatsApp → Linked Devices. Needs system `libmagic`. |
| `twilio` | ✅ | ❌ | Outbound-only in the isolated runtime — Twilio inbound needs a public HTTP webhook, which the stdio service does not host. `whatsapp_status` reports `inbound_supported: false`. |

A freshly queued message is reported as **PENDING**, not delivered — the agent
will not claim delivery it cannot confirm.

## Install

The host installs the `[service]` extra into a per-agent venv automatically when
the feature is enabled. To develop locally:

```bash
uv pip install -e /path/to/kestrel-channel-whatsapp            # host-side marker
uv pip install -e "/path/to/kestrel-channel-whatsapp[service]" # + transport, to test the service
```

Add `WhatsAppFeature` to the agent's `features` list in `multi_agent.toml`, then
restart the host. The feature appears in the agent's features UI with a config
form.

## Configuration

Set via the features UI / persisted config (forwarded to the service through the
initialize handshake), or via environment variables (UI/persisted config wins;
env is a fallback):

| Config key | Env var | Notes |
| --- | --- | --- |
| `provider` | `KESTREL_WHATSAPP_PROVIDER` | `web` (default) or `twilio` |
| `session_db` | `KESTREL_WHATSAPP_SESSION_DB` | web: neonize companion-session SQLite path |
| `allowed_senders` | — | E.164 allowlist; empty = allow all |
| `account_sid` | `TWILIO_ACCOUNT_SID` | twilio only |
| `auth_token` | `TWILIO_AUTH_TOKEN` | twilio only (secret) |
| `from_number` | `TWILIO_WHATSAPP_FROM` | twilio sender, E.164. Defaults to the sandbox `+14155238886` |

## Linking the web provider

1. Enable the feature with `provider="web"` and restart the host.
2. Call `whatsapp_link` (or `!whatsapp link`) — it returns a pairing QR.
3. On your phone: WhatsApp → Settings → Linked Devices → Link a Device → scan.
4. Once linked, `whatsapp_status` reports `connected: true`; the agent receives
   inbound messages as `channel.message` signals and replies via `channels_send`.
