Spaces:
Runtime error
Engine Capability Matrix
This page is the operator- and engineer-facing view of which IWhatsAppEngine capabilities are real on each adapter β wwjs (whatsapp-web.js, the default) and baileys (the browser-free alternative) β and, for the ones that are missing, why and where to start.
The committed source of truth is src/engine/engine-capability-matrix.ts. Every IWhatsAppEngine method has a row with two adapters; each adapter is either supported (works end-to-end) or not-available, and each not-available cell carries a rootCause:
adapter-gapβ the underlying library already HAS the capability; only the OpenWA adapter wiring is missing. Fixable in this repo with a PR that calls the library symbol the evidence points at.library-limitationβ the underlying library exposes no first-class symbol for the operation. Not fixable without a raw-proto/fork effort or an event-cache hack.uncertainβ the source trace was inconclusive; needs a live spike.
Each row also cites an evidence string (the exact library file:symbol inspected), so an engineer can open the file and start wiring immediately.
How the matrix stays honest
src/engine/engine-parity.spec.ts is a structural gate: it verifies the matrix keys correspond exactly to IWhatsAppEngine methods and checks the adapter throw-invariants it can observe through Class.prototype.method.toString(). No engine is instantiated and no Chromium/socket is opened. The gate does not read this document and cannot detect a non-throwing phantom stub becoming real (or regressing), so status/evidence changes still require source review and a deliberate documentation update.
The rootCause/evidence fields are hand-curated from source traces of the installed libraries (node_modules/@whiskeysockets/baileys, node_modules/whatsapp-web.js); they are not regenerated by the spec and are updated by hand as adapters are wired or libraries change.
Phantom support. The drift gate's throw-heuristic cannot see adapter methods that silently stub (return
null/[]+ a warn log) without throwing. Three wwjs entries βgetCatalog,getProducts,getProductβ are markednot-availablein the matrix even though their adapter bodies do not throw, because the library has no API for them. When the drift gate is extended to assert against this matrix, it must consultstatus, not just the throw pattern, for these rows (or the adapter stubs must start throwing).
Unwired-capability inventory
15 of the 80 interface methods are not-available on at least one adapter (20 not-available adapter-cells total). Grouped by cluster below. Each entry shows: status today β rootCause β evidence β wiring note.
Channels / Newsletter
| Method | baileys | wwjs |
|---|---|---|
getChannelMessages |
not-available β adapter-gap | supported |
getSubscribedChannels |
not-available β library-limitation | supported |
Wired. β
getChannelById,subscribeToChannel,unsubscribeFromChannelon Baileys β vianewsletterMetadata('jid'|'invite', β¦)βNewsletterMetadatamapped toChannel(id/name/description/inviteCode/subscriberCount/picture/verified/createdAt),newsletterFollow(subscribe, inviteβjid bridge),newsletterUnfollow(unsubscribe, 1:1).getChannelByIdon Baileys resolves ANY channel by jid (richer than the wwjs subscribed-list lookup).
getChannelMessages(baileys, adapter-gap).sock.newsletterFetchMessages(channelId, limit, 0, 0)(Socket/newsletter.d.ts:19) returns the rawBinaryNodeof<message_updates>children (newsletter.js:149). The fetch is one line; the real work is walking the children and mapping each toChannelMessage{id,body,timestamp,hasMedia}β no library parser is exposed. Not wired: a hand-written BinaryNode walk can't be verified without a live WhatsApp session, so it stays a documented gap rather than an unverified implementation.getSubscribedChannels(baileys, library-limitation). No enumerate-subscribed-newsletters query in the library. All 23Socket/newsletter.d.tsexports are per-jid (newsletterMetadatarequires a key;newsletterSubscribers(jid)returns the count of one newsletter). Thenewsletterevent surfaces jids opportunistically during live sync, but that is incremental, not a list-all. Would require a raw WMex/app-state hack against an undocumented XWAPath.
Labels (WhatsApp Business)
| Method | baileys | wwjs |
|---|---|---|
getLabels |
not-available β library-limitation | supported |
getLabelById |
not-available β library-limitation | supported |
getChatLabels |
not-available β library-limitation | supported |
Wired. β
addLabelToChat/removeLabelFromChaton the Baileys engine β 1:1 tosock.addChatLabel(chatId, labelId)/sock.removeChatLabel(chatId, labelId)(Socket/chats.d.ts:70-71). WhatsApp-Business-only (rejects on personal accounts). Do not useaddLabel(jid, LabelActionBody)(chats.d.ts:69) β that creates/edits the label definition, not the chat association.
getLabels/getLabelById/getChatLabels(baileys, library-limitation). No label read/fetch symbol anywhere inlib/**/*.d.ts(Types/Label.d.tshas only the interface +LabelColorenum;chats.d.ts/business.d.tsexpose only writes). Label data does arrive via app-state sync (messaging-history.set), so a determined adapter could capture+cache labels from the event stream β but that is a relay/cache hack, not a first-class getter, and there is no network fetch to seed/refresh it on demand.
Catalog / Products / Orders (WhatsApp Business)
| Method | baileys | wwjs |
|---|---|---|
getCatalog |
not-available β adapter-gap (medium-confidence) | not-available β library-limitation |
getProducts |
not-available β adapter-gap | not-available β library-limitation |
getProduct |
not-available β adapter-gap (medium-confidence) | not-available β library-limitation |
sendProduct |
not-available β adapter-gap | not-available β library-limitation |
sendCatalog |
not-available β library-limitation | not-available β library-limitation |
getProducts(baileys, adapter-gap).sock.getCatalog({jid, limit, cursor})(Socket/business.d.ts:7) returns{products, nextPageCursor}β paginated, maps toPaginatedProducts. Caveat: cursor-based, sototalis unknown (approximate or iterate).getCatalog(baileys, adapter-gap, medium-confidence).getCatalogreturns a product list + cursor, not the OpenWACatalogmetadata wrapper. Name needsgetCollections(jid)(business.d.ts:11);id/description/urlhave no source β the adapter would synthesize a partial Catalog (productCount=products.length, rest best-effort).getProduct(baileys, adapter-gap, medium-confidence). No directgetProduct(id); callgetCatalog({jid,limit})thenproducts.find(p=>p.id===productId)β loads the whole page to fetch one product.sendProduct(baileys, adapter-gap).AnyRegularMessageContentaccepts{product: WASendableProduct, businessOwnerJid, body}(Types/Message.d.ts:203, built inmessages.js:397). Two-step wiring:getCataloglookup to resolve the product's image/title/price, thensock.sendMessage(chatId, {product:{...}, body}).productId-only send without the lookup is not possible.sendCatalog(both, library-limitation). No catalog-share message type exists in either library. BaileysAnyMessageContenthas only{product}(single product); the catalog CRUD nodes (Socket/business.js:294-362) mutate the catalog, they don't send it. Would require raw-proto relay hacks (unverified).- wwjs catalog (library-limitation).
whatsapp-web.jshas noClient.getCatalog/getProducts/getProduct/sendProduct/sendCatalog(index.d.tsgrep = 0 hits).Product/Orderare inbound-only parsers. The adapter currently stubs the reads tonull/[]without throwing (phantom support).
Status β post / delete
Wired. β
postTextStatus/postImageStatus/postVideoStatus+deleteStatuson whatsapp-web.js. Posts route viasendMessage('status@broadcast', β¦)({ extra: { backgroundColor, fontStyle } }for text;{ caption }for media);deleteStatuscallsrevokeStatusMessage(statusId)(own-status only). Caveat: whatsapp-web.js has no status-recipient arg, soStatusPostOptions.recipientsis not honored on this engine (it broadcasts to the account's status-privacy audience; a one-time warning is logged). The Baileys engine honorsrecipients(statusJidList).
deleteStatus(baileys) caveat. Markedsupported(no throw), but the adapter self-describes itssendMessage(status@broadcast,{delete})revoke shape as empirically unverified (baileys.adapter.ts:909-911) β only posting was live-spiked. May need a fallback toEngineNotSupportedErrorif WA rejects the shape.
Status β read (contact stories)
| Method | baileys | wwjs |
|---|---|---|
getContactStatus |
not-available β library-limitation | supported |
getContactStatuses |
not-available β library-limitation | supported |
Wired. β
getContactStatus/getContactStatuseson whatsapp-web.js βgetBroadcastById(id)/getBroadcasts()flattened toStatus[](contact viabroadcast.getContact(); type fromMessageTypes; 24h TTL). Caveat:Status.typeis thetext|image|videounion β audio/other story types collapse totext.
getContactStatus/getContactStatuses(baileys, library-limitation).fetchStatus(Socket/chats.d.ts:42viaUSyncStatusProtocol) returns the about/profile text line ({status, setAt}), not 24h stories. No story-read getter exists; story broadcasts surface only asstatus@broadcastmessages viamessages.upsert/messaging-history.setevents. Would require OpenWA to accumulatestatus@broadcastmessages itself and project them intoStatus[].
Messaging misc β delete / history / reactions
| Method | baileys | wwjs |
|---|---|---|
getChatHistory |
not-available β library-limitation | supported |
getMessageReactions |
not-available β library-limitation | supported |
getChatHistory(baileys, library-limitation). The only history primitive isfetchMessageHistory(count, oldestMsgKey, oldestMsgTimestamp)(Socket/business.d.ts:25) β it returns a sync-token string, not messages; the messages are delivered later via themessaging-history.setevent. There is no per-chatfetchMessages(chatId, limit)on the socket. A synchronousPromise<IncomingMessage[]>for one chat would require an OpenWA-side chat-indexed store populated frommessages.upsert+messaging-history.setevents.getMessageReactions(baileys, library-limitation). No on-demand server fetch. Reactions exist only as event-augmented state onWAMessage.reactions(proto.IReaction[]atWAProto/index.d.ts:10623), mutated byupdateMessageWithReactionand surfaced via themessages.reactionevent. The adapter already processesreactionMessageevents (baileys.adapter.ts:1048-1057) and emitsonMessageReaction, but it does not persist.reactionsinto itsmessageStore(early-returns at line 1058). A store-backed read would need that persistence added first; even then, only reactions observed since session start are known (no historical backfill).
Prioritized roadmap β adapter gaps (fixable in this repo)
These are the capabilities the underlying library already supports but the OpenWA adapter does not wire. Ranked high-value + low-effort first. Each is a self-contained backlog item; an engineer can open the cited symbol and start.
Progress. β
deleteMessage(forEveryone=false, Baileys) β wired viachatModify({ deleteForMe }); moved tosupported.
Tier 1 β small effort, high value β shipped
All Tier-1 adapter-gaps have been wired:
- β
deleteMessage(forEveryone=false)β Baileys (chatModify({ deleteForMe })) - β
postTextStatus/postImageStatus/postVideoStatusβ whatsapp-web.js (sendMessage('status@broadcast', β¦);recipientsnot honored) - β
addLabelToChat/removeLabelFromChatβ Baileys (addChatLabel/removeChatLabel; WhatsApp-Business-only) - β
getChannelById/subscribeToChannel/unsubscribeFromChannelβ Baileys (newsletterMetadata/newsletterFollow/newsletterUnfollow)
Tier 2 β small-to-medium effort, medium-high value
All Tier-2 items wired (see progress above). Remaining channel work is Tier 3: getChannelMessages (raw BinaryNode, no library parser).
Tier 3 β medium effort
| # | Method : engine | Library call to wire | Effort | Value |
|---|---|---|---|---|
| 13 | getChannelMessages : baileys |
sock.newsletterFetchMessages(jid,count,since,after) (Socket/newsletter.d.ts:19) + hand-written BinaryNodeβChannelMessage parser |
M | Read channel posts. The fetch is 1 line; the BinaryNode parsing/normalization is the real work (no library parser exposed). |
| 14 | getProducts : baileys |
sock.getCatalog({jid,limit,cursor}) β {products, nextPageCursor} (Socket/business.d.ts:7) |
M | Commerce. Derive pagination.total/totalPages (cursor-based, total unknown β approximate or iterate). |
| 15 | sendProduct : baileys |
2-step: getCatalog lookup (image/title/price) then sendMessage({product:{...},body}) (Types/Message.d.ts:203) |
M | Outbound commerce. productId-only send is not possible without the lookup. |
| 16 | getCatalog : baileys |
getCatalog + getCollections (Socket/business.d.ts:7,11); synthesize partial Catalog (medium-confidence shape) |
M | Catalog metadata. Fields id/description/url have no source β best-effort. |
Tier 4 β small effort, lower value
| # | Method : engine | Library call to wire | Effort | Value |
|---|---|---|---|---|
| 17 | getProduct : baileys |
getCatalog({jid,limit}) then products.find(p=>p.id===productId) (Socket/business.d.ts:7) |
S | Single-product read. Inelegant β loads a whole page to fetch one product. Ship after #14 reuses the same catalog call. |
Library limitations β not available without raw-proto/fork effort
These are honestly out of reach of a clean adapter wiring because the installed library exposes no first-class symbol. Listed so operators can plan around them rather than file unactionable bugs.
baileys (9 cells):
getSubscribedChannelsβ no enumerate-newsletters query; allSocket/newsletter.d.tsexports are per-jid. Needs a raw WMex/app-state hack.getLabels/getLabelById/getChatLabelsβ no label read symbol; only writes (Types/Label.d.tsis types-only). Workaround: capture labels from themessaging-history.setapp-state event into an in-memory cache (relay hack, no on-demand refresh).getChatHistoryβ onlyfetchMessageHistory(event-delivered sync token); no synchronous per-chatfetchMessages. Needs an OpenWA-side chat-indexed store fed frommessages.upsert+messaging-history.set.getMessageReactionsβ no on-demand fetch; reactions only arrive via themessages.reactionevent. Partial local path: persist each event into themessageStore, then read (no historical backfill).getContactStatus/getContactStatusesβfetchStatusreturns the about text, not 24h stories; stories only surface asstatus@broadcastmessages. Needs an OpenWA-side story accumulator.sendCatalogβ no catalog-share message type inAnyMessageContent(only single{product}).
wwjs (6 cells):
getCatalog/getProducts/getProductβ no catalog API at all (index.d.ts0 hits;Productis inbound-only).sendProductβ no outbound product content type.sendCatalogβ no outbound catalog content type.setGroupEphemeralβ no disappearing-timer setter in whatsapp-web.js 1.34.7 (ephemeralDurationexists only as a createGroup option). Baileys:groupToggleEphemeral.
Snapshot summary
- 80 interface methods, 160 adapter-cells (80 Γ 2 engines).
- 140 supported cells; 20 not-available cells across 15 methods.
- Of the 20 not-available cells: 5 adapter-gaps (fixable) + 15 library-limitations + 0 uncertain.
- 3 phantom-support rows (wwjs
getCatalog/getProducts/getProductβ they stub without throwing, so the drift gate's throw-heuristic cannot see them; the matrix is the source of truth for these).