@chamvi/chat-whatsapp

中文文档
0.8.0发布于 2026/11/09Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 1699

WhatsApp Web on Baileys as a chat channel for chamvi agents.

原始使用文档
文档目录7 节
  1. Installation
  2. Usage
  3. Limits
  4. Operations
  5. With @chamvi/app
  6. API
  7. License

@chamvi/chat-whatsapp

@chamvi/chat-whatsapp

WhatsApp Web for chamvi agents.
A chamvi-owned Chat SDK adapter on Baileys,
plugged into an @chamvi/chat channel.

npm version CI License


Table of Contents

Installation

npm install @chamvi/chat-whatsapp @chamvi/chat @chamvi/core @chamvi/http chat [email protected] --save

ESM only, Node.js >=26.0.0. @chamvi/chat, @chamvi/core, chat (the Chat SDK, 4.44 or later in the 4.x line) and baileys are peer dependencies; baileys is pinned to the one version chamvi's adapter was verified against (7.0.0-rc14). smob is the only runtime dependency. The package uses only @chamvi/chat's public API: its channel is a chatChannel() with the WhatsApp hooks passed as platform (IChatPlatform).

Usage

createWhatsAppChat() builds a Chat SDK Chat, the WhatsApp adapter and its chatChannel(). Register the channel on the engine, subscribe an agent with channel.on() and reply from a tool with ctx.channels.findOneOrFail('chat').emit():

import type { LanguageModel } from 'ai';
import { z } from 'zod';
import {
    Engine,
    createMemoryStore,
    defineModelAgent,
    defineTool,
} from '@chamvi/core';
import type { IEngine } from '@chamvi/core';
import { createWhatsAppChat } from '@chamvi/chat-whatsapp';
import type { WhatsAppChatOptions } from '@chamvi/chat-whatsapp';

export function createWhatsAppEngine(
    model: LanguageModel,
    // auth: your secure session store; state: a Chat SDK state adapter
    options: Pick<WhatsAppChatOptions, 'auth' | 'state' | 'onPairing' | 'socketFactory' | 'clock'>,
): IEngine {
    const { WHATSAPP_SENDERS, APP_URL } = process.env;

    if (!WHATSAPP_SENDERS || !APP_URL) {
        throw new Error('Set WHATSAPP_SENDERS (phone number jids, comma separated) and APP_URL.');
    }

    const whatsapp = createWhatsAppChat({
        // a non-secret alias of the account, not its phone number
        account: 'assistant',
        // only these senders start runs; everybody else is ignored
        policy: { senders: WHATSAPP_SENDERS.split(',') },
        resolveInvoker: (identity) => ({
            id: identity.sender,
            kind: 'whatsapp',
            permissions: [],
        }),
        // a link to the approval in the chamvi UI: ids only, answered there
        approvalUrl: ({ runId, approvalId }) => `${APP_URL}/ui/approvals?run=${encodeURIComponent(runId)}&approval=${encodeURIComponent(approvalId)}`,
        ...options,
    });

    const reply = defineTool({
        description: 'Reply in the chat the message came from.',
        inputSchema: z.object({ text: z.string() }),
        execute: async ({ text }, { context }) => {
            // the source chat: no address needed
            await context.channels.findOneOrFail('chat').emit({ body: text });

            return 'sent';
        },
    });

    const assistant = defineModelAgent({
        name: 'assistant',
        model,
        instructions: 'You are the owner\'s assistant on WhatsApp. Answer with the reply tool.',
        // one memory per direct chat and one per group
        conversation: true,
        // direct messages and mentions in an allowed group
        channels: [whatsapp.channel.on()],
        tools: { reply },
    });

    return new Engine({
        // memory store: conversations included; use @chamvi/store-db in production
        store: createMemoryStore({ clock: options.clock }),
        agents: [assistant],
        channels: [whatsapp.channel],
        clock: options.clock,
        conversations: { authorize: ({ agent }) => agent === 'assistant' },
    });
}

Nothing connects when the setup is built: the channel's start (the engine's) initializes the Chat, acquires the auth lease, then builds the socket. While the account is not paired, onPairing receives a QR reference to render for the phone (it rotates); it never reaches a log or onError. setup.status() (and onStatus) reports stopped, connecting, pairing, connected, disconnected, logged-out, held-elsewhere (another process holds the session; the start retries instead of failing) or failed (the session store failed); setup.reset() clears the session and pairs again without a restart (refused while held-elsewhere).

auth is an IWhatsAppAuthProvider: createStoreWhatsAppAuth({ vault, connection }) keeps the session sealed in a chamvi store (a connection vault of @chamvi/core), or supply your own (see Operations). With @chamvi/app, whatsappModule() wires the store provider for you. state is a Chat SDK StateAdapter; chamvi's intake does not depend on its deduplication.

Who may start runs is policy:

PolicyAllows
{ senders: [...] }Direct messages of the listed phone number jids (<digits>@s.whatsapp.net, no device suffix)
groups: [...]Messages in the listed group jids (<id>@g.us) from an allowed sender, by default only when they mention the bot (requireGroupMention: false drops that)
{ open: true }Any resolved sender; resolveInvoker decides (undefined denies)

A message starts runs only when its identity is authenticated, the policy allows it and resolveInvoker names an invoker. The bot's own messages, unknown senders and a sender whose LID cannot be resolved to a phone number through Baileys' authenticated mapping are denied silently: no run, no reply, no download.

Subscriptions, replies, approvals, memory and trust follow @chamvi/chat: the conversation of a message is { account, chat jid, direct:<sender> | group:<group jid> }, so a group shares one memory among its members.

Limits

  • Text only, no media download, no history, no message splitting (CHAT_TEXT_MAX_CHARS), as for every chat channel.
  • Delivery is at least once: each send carries a WhatsApp message id derived from the message key, so a retry reuses it; whether WhatsApp drops such a duplicate is not verified.
  • No lossless or offline ingestion. A message that arrives while the connector is down, or that the process loses before it is stored, is not fetched later.

Operations

WhatsApp Web through Baileys is an unofficial protocol: WhatsApp may change it or restrict the account. Use an account you can afford to lose and read the WhatsApp guide before a live cutover. In short:

  • Session store. createStoreWhatsAppAuth() (and whatsappModule()) store creds and signal keys AES-256-GCM sealed under CHAMVI_ENCRYPTION_KEYS, every batch atomic and fenced by the lease connection:<name> (TTL 60 s, renewed every 20 s): after a kill -9 the next start shows held-elsewhere and connects once the lease expired. Losing the key means a reset and pairing again (the reset recreates the connection under a fresh data key). A provider of your own (acquire(signal, { reset? }) -> { auth, saveCreds, flush, release, lost? }) must persist creds and signal keys atomically and serialized, take the location exclusively (a second holder refused with ConnectionHeldError) and, on reset, empty the session once it holds the location. chamvi guards every auth call with the lease's generation, flushes before releasing and releases the lease on every close: a failed flush is reported, and writes that outlive WHATSAPP_SHUTDOWN_TIMEOUT_MS (30 s) are reported and released once they settle. Ending the socket has its own bound of the same length, so a dead link never leaves the lease held.
  • One connector. Run one deployment per account, Recreate/stop-then-start, never two replicas.
  • Pairing is by QR only (onPairing, or the dashboard's Connections page with whatsappModule()); no phone-number code.
  • Reconnect: an unexpected close reconnects with backoff (WHATSAPP_RECONNECT: 2 s growing to 30 s, 12 attempts). A logout (401) or a replacing session (440) does not: reset (setup.reset(), or Reset session on the dashboard) and pair again.
  • Stop ends the socket, refuses later auth writes and flushes. Baileys may retry a decrypt whose key commit the stop refused (for about 30 s, to nothing); chamvi does not guarantee that such a message is processed after the restart.
  • Logging: Baileys' own logger is silent (it would carry pairing material), which also hides its decrypt and LID warnings.

With @chamvi/app

import { createApp } from '@chamvi/app';
import { whatsappModule } from '@chamvi/chat-whatsapp/module';

const whatsapp = whatsappModule({
    // the session's connection: sealed in the app store, shown at /ui/connections/whatsapp
    connection: 'whatsapp',
    account: 'assistant',
    state,
    policy: { senders: ['[email protected]'] },
    resolveInvoker: (identity) => ({ id: identity.sender, kind: 'whatsapp', permissions: [] }),
});

export default createApp({
    // agents subscribe with whatsapp.on()
    agents: [assistant],
    modules: [whatsapp],
    conversations: { authorize: ({ agent }) => agent === 'assistant' },
});

whatsappModule() (@chamvi/app is an optional peer) takes createWhatsAppChat()'s options without auth, onPairing and onStatus, plus connection, leaseTtlMs and an optional approvalUrl (default: the dashboard under CHAMVI_PUBLIC_URL). In setup it needs CHAMVI_ENCRYPTION_KEYS (<id>:<base64 of 32 bytes>, comma separated, the first active; setup fails naming it when unset or malformed), contributes the channel to AppToken.CHANNELS and the connection handle to AppToken.CONNECTIONS: the dashboard's Connections page shows the status and, to connection:<name>:write holders only, the live QR and Reset session. The lease is taken only when the engine starts the channel, so chamvi start --check and chamvi run-job acquire and write nothing.

With a provider of your own, chatModule({ channel: setup.channel }) of @chamvi/chat/module contributes a createWhatsAppChat() channel instead.

API

ExportHolds
createWhatsAppChat(options){ chat, ingress, channel, status(), reset(), conversation() }
createStoreWhatsAppAuth({ vault, connection, owner?, leaseTtlMs? })The store-backed IWhatsAppAuthProvider; WHATSAPP_CONNECTION_TYPE 'whatsapp'
whatsappModule(options) (/module)WhatsAppModule { channel, handle, on(), to(), conversation(), status(), reset() }, WhatsAppModuleOptions
Types WhatsAppChatOptions, WhatsAppChatSetup, StoreWhatsAppAuthOptions, WhatsAppPolicy, WhatsAppIdentity, IWhatsAppAuthProvider, WhatsAppAuthLease, WhatsAppPairing, WhatsAppConnectionStatus, WhatsAppSocket, WhatsAppSocketFactory
WHATSAPP_ADAPTER_NAME 'whatsapp', WHATSAPP_RECONNECT, WHATSAPP_SHUTDOWN_TIMEOUT_MS 30 000
Error codes whatsapp_auth, whatsapp_connection, whatsapp_logged_out (WHATSAPP_*_ERROR_CODE)

The full option reference is the WhatsApp guide.

License

Made with 💚

Published under Apache License 2.0.

版本与发布记录

@chamvi/chat-whatsapp 共发布 10 个版本,首次发布于 2026/11/02;过去 12 个月发布了 10 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.8.0latest2026/11/091 天前
0.7.32026/11/064 天前
0.7.22026/11/054 天前
0.7.12026/11/055 天前
0.7.02026/11/045 天前
0.6.12026/11/046 天前

关于 @chamvi/chat-whatsapp 的常见问题

@chamvi/chat-whatsapp 是什么?

WhatsApp Web on Baileys as a chat channel for chamvi agents.

如何安装 @chamvi/chat-whatsapp?

在项目目录运行 npm install @chamvi/chat-whatsapp,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:chat(^4.44.0)、baileys(7.0.0-rc14)、@chamvi/chat(^0.8.0)、@chamvi/core(^0.8.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@chamvi/chat-whatsapp 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@chamvi/chat-whatsapp 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@chamvi/chat-whatsapp 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=26.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@chamvi/chat-whatsapp 可以商用吗?

@chamvi/chat-whatsapp 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@chamvi/chat-whatsapp 的下载量有多少?

根据 npm 官方下载统计,@chamvi/chat-whatsapp 在 2026/10/10 – 2026/11/08 这 30 天内被下载 1,699 次,日均约 57 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@chamvi/chat-whatsapp 还在维护吗?

最新版本 0.8.0 发布于 2026/11/09(1 天前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/11/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。