Skip to content

feat(connections): WhatsApp-Web personal-account QR pairing (Baileys, opt-in)#10

Merged
ogarciarevett merged 1 commit into
mainfrom
feat/whatsapp-web-pairing
Jun 5, 2026
Merged

feat(connections): WhatsApp-Web personal-account QR pairing (Baileys, opt-in)#10
ogarciarevett merged 1 commit into
mainfrom
feat/whatsapp-web-pairing

Conversation

@ogarciarevett

Copy link
Copy Markdown
Owner

Summary

Extends scan-to-connect (#9) to the personal-account channel: pair a personal WhatsApp by scanning the rotating QR in Vesper World or the terminal. Two-way once paired.

Unlike Telegram/Discord (bot-token + chat-link QR), WhatsApp-Web pairing is self-driving — the scan itself establishes the session — so it needs a reverse-engineered WhatsApp-Web client. That dependency (Baileys) is contained in a single opt-in package.

Isolation (the design constraint)

  • Core ships zero Baileys. The channel-plugin registry is now extensible (registerChannelPlugin); whatsapp-web is a catalog id + ChannelId (transport qr-web) whose available/pairable derive from runtime registration.
  • Baileys lives only in @vesper/channel-whatsapp-web — the sole runtime dependency. core + ui never reference it; the cli declares it as an optionalDependency and the daemon lazy-loads it at boot via a variable-specifier dynamic import() (loadOptionalChannels), so it stays out of tsc resolution + the compiled binary's static graph and a distributed build can --omit=optional.

What's included

  • PackageWhatsAppWebHandler (ChannelHandler + Pairable) with an injected WASocketFactory seam (tests inject a fake — no live WhatsApp, no real socket). makeVaultAuthState ports Baileys' useMultiFileAuthState to a single vault blob (whatsapp_web_session, BufferJSON-serialized). startPairing bridges connection.update to an async queue so the rotating QR yields repeated awaiting; open → save + linked; two-way receive; send over the live socket.
  • Coordinator — a self-driving pairing branch (pairingNeedsInbound: false): skips the authenticate precondition + the inbound multiplex, and enables-on-link even without a chat id. Telegram/Discord behavior is byte-identical (default true).
  • Contract.ai/context.md carves out the one opt-in dependency (cites the 2026-06-05 authorization; does not relax Hard rule 12 — Baileys is a channel transport, not an LLM SDK; not a precedent). bun run sync:ai regenerated AGENTS.md + rules.mdc.

Notes / known limitations

  • Baileys 7.0.0-rc13 (the latest dist-tag): current protocol, but an RC that pulls a native Rust bridge (whatsapp-rust-bridge) + libsignal/protobuf. Chosen deliberately over the Rust-free legacy 6.x. Installed + runtime-load verified.
  • vesper connections list (CLI process) shows whatsapp-web as unavailable — the plugin registers only in the daemon (the UI/daemon is the source of truth; the CLI doesn't load Baileys just to list). The compiled vesper-desktop binary omits it until Launch wires it.

Test plan

  • bun test — 870 / 0 fail (15 package tests: vault auth-state round-trip, rotating-QR pairing, linked/error/expired, receive mapping, send-guard; + 1 lazy-registration integration test).
  • biome ci clean.
  • tsc --noEmit adds no new errors; the new package's src/ is clean.
  • Isolation verified: Baileys is not resolvable from root/core; the whatsapp-web catalog addition broke zero existing tests.
  • Live pairing against a real WhatsApp account (not in CI — the socket seam is fully mocked; no live scan).

… opt-in)

Pair a personal WhatsApp account by scanning the rotating QR in Vesper World or
the terminal — extends scan-to-connect to the personal-account channel.

Baileys is isolated in a new opt-in @vesper/channel-whatsapp-web package (the sole
runtime dependency); core/ui never reference it, the cli declares it as an
optionalDependency + lazy-loads it via a variable-specifier dynamic import, so it
stays out of the static graph + compiled binary and is omittable.

- core: extensible channel-plugin registry (registerChannelPlugin) + whatsapp-web
  catalog/ChannelId (transport qr-web) + pairingNeedsInbound flag — zero Baileys in core
- package: WhatsAppWebHandler (ChannelHandler + Pairable) with an injected socket
  factory; vault-backed Baileys session (single whatsapp_web_session blob); rotating-QR
  pairing (async-queue bridge), two-way receive, send over the live socket
- daemon: loadOptionalChannels() registers the package at boot; coordinator gains a
  self-driving pairing path (skips authenticate + inbound multiplex; enables on link
  without a chatId) — Telegram/Discord unchanged
- contract: .ai/context.md carves out the one opt-in dependency (Baileys is a channel
  transport, not an LLM SDK — Hard rule 12 intact); sync:ai regenerated

870 tests / 0 fail; biome clean; Baileys 7.0.0-rc13. Authorized by Omar 2026-06-05.
Record per Rule 11 (issue cap): cycle-log.md.
@ogarciarevett ogarciarevett merged commit fb1a98b into main Jun 5, 2026
1 check passed
@ogarciarevett ogarciarevett deleted the feat/whatsapp-web-pairing branch June 5, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant