Skip to main content
The stable line (@botmesh/core 2.0.0, the other public packages 1.0.0) freezes the public API. The beta (@botmesh/core 2.0.0-beta, the other public packages 1.0.0-beta) already ships it, so these pages also cover upgrading from an alpha to the beta. Coming from a 2.0 alpha, most code keeps running: renamed APIs keep a @deprecated alias (your editor strikes them through), and the few hard breaks are marked Breaking on these pages.

Upgrade checklist

Work through it in order; each step links to the details.
  1. Upgrade every @botmesh/* package together, to the same release line. Mixing an old core with newer channel packages is not supported.
  2. Dependencies (details): install ffmpeg if your Baileys bot sends voice notes, move Gemini bots to @google/genai, check the redis peer range and the Graph API version, and update dashboards that read botwa_* metrics.
  3. Run the type checker (tsc --noEmit) and fix what it reports (details below): imports that moved to @botmesh/core/spi, /testing or /internal, @botmesh/ai/testing, @botmesh/baileys/internal and the removed RedisStore APIs.
  4. Replace the deprecated names (table): ctx.reply instead of ctx.say, flow instead of addKeyword, bot.send, bot.plug for plugins, bot.trigger, onType… They still work, but they are removed in v3.
  5. Review the behaviour changes (details). The two most bots need: lang: 'es' if your bot speaks Spanish (built-in texts are now English), and maxAttempts: N where a wait set retries: N.
  6. Read the notes for your channels and plugins (details).
  7. Run your tests and start the bot once with a bot.on('error', …) listener. Watch for the one-time retries warning and for ConfigValidationError, which now names any invalid duration option.

Package entries

The main entry (@botmesh/core) now exports only what an application needs (372 names, down from 512). Everything else moved to a subpath: Breaking: the names below are no longer exported by @botmesh/core. Change the import path; the values are the same.
Some types the engine moved still appear in signatures an application uses, so the main entry keeps them: MessagingFeature and MessagingSupport (ctx.supports()), ResolvedWaitOptions (ConversationControlsBase.waitAny()) and JsonValue (the shape of SerializedMediaRef.data and of the journal records; @botmesh/core/spi exports it too). Import them from @botmesh/core, not from /internal or /testing. The golden-transcript helpers (recordTranscript, RecordingProvider, redactTranscript, transcriptFromStore, parseTranscript…) are still re-exported by @botmesh/core, but deprecated: import them from @botmesh/core/testing.

One name per concept

These aliases keep working and are marked @deprecated: bot.enterConversation(handler, ctx) with an inline handler is not deprecated: it is the way to start a conversation that is not registered. BotConfig.storeConfig now has a type (file, maxMessagesPerChat, autoSaveInterval), and the bot logs a warning when it is set. Setting durability.backend and durability.journal to two different backends throws a ConfigValidationError.

Plugins: one instance per name

Breaking: bot.plug() throws when a plugin with the same name is already plugged. Before, the second instance was installed too and both ran their handlers. To plug a plugin again, call the unplug function first. A plugin built to run several times sets multiple: true in its spec:

Durations

Every duration option of BotConfig takes a duration string or a number of milliseconds, including all *Ms options. typing.minMs, typing.maxMs and the durability backoff.baseMs / backoff.capMs accepted only numbers before; they now accept '800ms', '1s' and so on. Breaking (stricter validation): new Bot() now parses every duration option and throws a ConfigValidationError naming it (err.field === 'coordination.turnWaitMs') when a value cannot be read. Before, some invalid values only failed later, with a message that did not name the option, and null was accepted for a few of them.

Logger

The LoggerLike contract is unchanged, and its calling convention is now documented: the packages call logger.warn('message') or logger.warn({ err, ...context }, 'message'), as pino does. See Logging.

Channel and store packages

The public channel and store packages follow the same rule: one name per concept, the old one kept as a @deprecated alias until v3. Every provider’s low-level error mapper is now named to<Channel>Error; the channels that were private during the alphas got the same renames (toDiscordError, toEmailError, and toTikTokError on the still-private TikTok channel), and @botmesh/messenger and @botmesh/instagram, public from the beta, ship only toMessengerError and toInstagramError. The behaviour changes of these packages (Telegram poll votes, Discord edits, SQLite schema versioning) are in Upgrade notes: channels & plugins.

@botmesh/baileys: entries and native types

The main entry keeps what an application needs (BaileysProvider, its options, toJid, isEditedMessage, RateLimiter, the PollStore types). The rest moved to @botmesh/baileys/internal, which has no semver guarantee; the old names stay on the main entry as @deprecated aliases until v3.
  • Breaking: toInbound and toOutbound are no longer exported by @botmesh/baileys: their signatures use Baileys’ own types. Import them from @botmesh/baileys/internal.
  • Breaking (types only): Baileys’ native values are opaque. provider.socket is BaileysSocket | null, provider.auth is BaileysAuthState, and the raw / native of messages, refs and send results are BaileysNativeMessage; the three are unknown aliases. Nothing changes at runtime. Use socketOf(provider) and authStateOf(provider) from @botmesh/baileys/internal for typed handles, or cast with your own @whiskeysockets/baileys import:
  • The JID predicates (isGroup, isPn, isLid, isBroadcast, isNewsletter) return boolean instead of boolean | undefined (undefined is now false).
  • pairingCode is renamed pairingPhoneNumber (the value was always the phone number); passing both with different numbers throws a TypeError.

@botmesh/redis-store

Breaking: the cluster key-layout internals moved to a new subpath without a semver promise, @botmesh/redis-store/internal. The layout option, the RedisKeyLayout type, migrateToClusterLayout(), inspectKeyspace(), isRedisClusterClient() and withClusterRetry() stay on the main entry. Breaking: two deprecated APIs were removed.

@botmesh/ai entries

@botmesh/ai got the same split as core: 135 names on the main entry before, 98 after. MockAIProvider, MockOptions and MockTurn are still re-exported by @botmesh/ai, but deprecated: import them from @botmesh/ai/testing. Breaking: the names below are no longer exported by @botmesh/ai. Change the import path; the values are the same. The client contracts did not change shape: their request and response types are now written inline, so a fake client typed AnthropicLike (or OpenAILike, GeminiLike…) compiles as before. To name a response shape, derive it from the contract or import it from /internal:
Breaking (frozen data): PRESETS, DEFAULT_YES, DEFAULT_NO, AI_MESSAGES and AI_LOCALES are deep-frozen, and typed readonly. Changing them used to change the defaults of every bot in the process; now it throws a TypeError in strict mode. Extend a copy:
Passing them unchanged still compiles (toolConfirm.yes/.no accept a readonly string[]). Provider constructors now take an AIProviderConfig (model, maxTokens, effort, thinking, baseUrl), the part of AIConfig a provider reads. Passing a whole AIConfig held in a variable still compiles, but an object literal with plugin fields now fails TypeScript’s excess-property check (TS2353), because providers never read them:
GenerateInput.onStreamEnd receives a named AIStreamEndInfo; its contract (once, after the last chunk, only on a normal end) is in Upgrade notes: channels & plugins.

HTTP, Graph and scaffolder packages

@botmesh/http: closed SecureFetchOptions. The options of secureFetch and fetchBuffer no longer extend RequestInit. They hold the request fields the client honours (method, headers, body, signal) and its policy (allowHttp, allowPrivateHosts, timeoutMs, maxBytes, maxRedirects, resolver, fetch).
  • Breaking (types only): redirect is gone. It was always overridden with 'manual', because the client follows redirects itself and validates each hop; use maxRedirects (0 refuses any redirect).
  • The other RequestInit fields (cache, keepalive, integrity…) still type-check through a @deprecated overload (LegacySecureFetchOptions) and are still forwarded to fetch. They stop being accepted at stable.
  • Streaming uploads need no extra field: with a ReadableStream (or async iterable) body, the client sets the duplex: 'half' that fetch requires. Drop an explicit duplex along with the other RequestInit fields.
  • A variable typed SecureFetchOptions no longer accepts browser fields (mode, credentials, window…). Remove them.
  • resolvePublicHost is now resolveHost: it resolves any host and leaves the verdict to assertSafeTarget. The old name stays as a @deprecated alias.
@botmesh/graph-core: one options object. checkGraphSignature takes { body, headers, appSecret, logger }. The positional form keeps working as a @deprecated overload with the same results. graph-core is a public package and is covered by semver like the others: a breaking change in it is a major bump.
@botmesh/meta: session-window plumbing is experimental. What an app configures and queries stays public and covered by semver: SessionWindowManager (constructor, forScope, state, isOpen, expiresAt, pending, clearPending), SessionWindowOptions, the SessionWindowStore contract and its MemorySessionWindowStore, the report and reopen types, SessionWindowReopenedError and the constants. The hooks the provider drives (send, noteInbound, noteFailedStatus) and their RawSend type are now @experimental: they may change in a minor release. Nothing changes at runtime. See API stability. create-botmesh: internals in /internal. The main entry is the scaffolder: scaffold, sanitizeName, getTemplate and TEMPLATE_NAMES. TEMPLATES, agentsMd, findBotMeshMonorepo and AgentsMdInfo moved to create-botmesh/internal (no semver); the main entry re-exports them as @deprecated until stable. The CLI does not change.

Sandbox API token

@botmesh/sandbox is private, but scripts that drive its web API change:
  • Breaking: POST /api/<channel>/send|fault|status|reset now needs the per-run token in the x-sandbox-token header. Read it from sandbox.token (or the CLI output), or fix it with startSandbox({ token }) / --token.
  • Requests whose Host is not loopback, or whose Origin is another site, get a 403. Pass allowedHosts / --allow-host when the hub is reached through another name on purpose.
  • Bad input is now a 4xx: unknown fields, wrong types and a body that is not a JSON object are refused, and toUserInput() throws SandboxInputError.

Next steps