Conversation
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughThis pull request introduces the Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client Code
participant Adapter as BeeperDesktopAPIAdapter
participant WS as WebSocket
participant API as Beeper Desktop API
participant Chat as Chat Interface
Client->>Adapter: initialize()
Adapter->>API: GET /v1/info
API-->>Adapter: server info + headers
Adapter->>Adapter: detectServerInfo()
Adapter-->>Client: initialized
Client->>Adapter: startEventsListener(chatIds)
Adapter->>WS: connect(wsEventsUrl)
WS-->>Adapter: connected
Adapter->>WS: subscribe({chatIds})
WS-->>Adapter: subscription ack
loop Event Processing
WS->>Adapter: message.upserted event
Adapter->>Adapter: process payload
Adapter->>Adapter: toChatMessage(beeperMsg)
Adapter->>Adapter: handle reactions
Adapter->>Chat: emit("newMessage", chatMsg)
Chat-->>Client: message received
end
WS->>Adapter: reaction.added event
Adapter->>Adapter: update reaction state
Adapter->>Chat: emit("reaction", reactionEvent)
Client->>Adapter: postMessage({text, threadId})
Adapter->>API: POST /v1/chat/.../messages
API-->>Adapter: message created
Adapter-->>Client: message posted
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
packages/chat-adapter/src/types.ts (1)
5-6: Pick one canonical bot user key in the public config.Exposing both
botUserIDandbotUserIdmakes the API ambiguous: callers can provide both with different values, and the type does not say which one wins. If the alias is needed for compatibility, deprecate one spelling and normalize/document precedence in the implementation instead of keeping both as first-class options.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat-adapter/src/types.ts` around lines 5 - 6, The public config exposes two ambiguous keys botUserID and botUserId; pick a single canonical key (choose one spelling, e.g., botUserId) in the types.ts declaration and remove the duplicate to avoid ambiguity, then update the runtime normalization logic (e.g., the config parsing/initialization path that reads external config) to accept the deprecated alias if present by mapping botUserID -> botUserId (or vice versa) and explicitly document the precedence (alias value is used only if the canonical key is missing). Ensure any references to both symbols in code, tests, and docs are updated to use the canonical name and add a short deprecation comment where the alias mapping is handled.packages/chat-adapter/src/index.ts (4)
703-711: Redundant property in factory function.
userName: config.userNameon line 709 is redundant sinceconfigis already spread on line 707. The spread already includesuserNameif present.♻️ Suggested cleanup
export function createBeeperDesktopAPIAdapter( config: BeeperDesktopAPIAdapterConfig, ): BeeperDesktopAPIAdapter { return new BeeperDesktopAPIAdapter({ ...config, logger: new ConsoleLogger("info", "beeper-desktop-api-adapter"), - userName: config.userName, }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat-adapter/src/index.ts` around lines 703 - 711, The factory function createBeeperDesktopAPIAdapter spreads config into the new BeeperDesktopAPIAdapter but then redundantly sets userName: config.userName; remove the redundant property so the returned object simply spreads ...config (and still sets logger) to avoid duplication and keep behavior unchanged—update the createBeeperDesktopAPIAdapter function to eliminate the explicit userName assignment.
113-138: Consider adding a connection timeout for WebSocket.The WebSocket connection has no timeout configured, which means if the server is unresponsive, the connection attempt could hang indefinitely. Consider adding a connection timeout or using the
handshakeTimeoutoption.💡 Suggested improvement
const wsURL = this.wsEventsUrl ?? `${this.baseUrl.replace(/^http/, "ws")}/v1/ws`; this.ws = new WebSocket(wsURL, { headers: { Authorization: `Bearer ${this.accessToken}`, }, + handshakeTimeout: 10000, // 10 second connection timeout });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat-adapter/src/index.ts` around lines 113 - 138, The WebSocket connection created in the constructor (this.ws = new WebSocket(wsURL, { ... })) has no connection timeout and can hang; update the WebSocket instantiation to include a handshake timeout (e.g., pass handshakeTimeout) and handle timeout errors by listening for the "error" or "close" events to trigger scheduleReconnect (respecting manualStopWS and autoConnectEvents) and set this.ws = null; ensure the timeout value is configurable or reasonable and that logging via this.logger.error/ warn records timeout-specific info so handleWSEvent, scheduleReconnect, manualStopWS and autoConnectEvents flow remains correct.
175-180: Consider makingforEveryoneconfigurable.The
forEveryone: falsevalue is hardcoded. Depending on use cases, callers may want to delete messages for everyone. Consider adding an optional parameter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat-adapter/src/index.ts` around lines 175 - 180, deleteMessage currently hardcodes forEveryone: false which prevents callers from deleting messages for all participants; change the deleteMessage signature to accept an optional parameter (e.g., options? or forEveryone?: boolean) with a default of false, update callers and tests as needed, and pass that value into the request body instead of the literal false so the function (and methods that call decodeThreadId and this.client.delete) can request deletion for everyone when desired.
420-429: Consider exponential backoff for reconnection.The reconnection logic uses a fixed delay regardless of how many times it has failed. For improved resilience, consider implementing exponential backoff with a maximum delay cap.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chat-adapter/src/index.ts` around lines 420 - 429, scheduleReconnect currently always uses wsReconnectDelayMs fixed delay; change it to exponential backoff by adding a retry state (e.g., reconnectAttempts or currentReconnectDelay) and a max cap (e.g., maxWsReconnectDelayMs). Compute delay = min(wsReconnectDelayMs * 2 ** reconnectAttempts, maxWsReconnectDelayMs) when scheduling, increment reconnectAttempts after scheduling, and keep existing guard on reconnectTimer. In the startEventsListener success path reset reconnectAttempts/currentReconnectDelay to initial value so future failures start from the base delay; preserve existing symbols (scheduleReconnect, reconnectTimer, wsReconnectDelayMs, startEventsListener, wsSubscriptions, logger.error) when implementing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chat-adapter/README.md`:
- Line 8: The quickstart install instruction only adds
`@beeper/chat-adapter-desktop-api` but the sample imports "chat" and
"@chat-adapter/state-memory"; update the README to either include the missing
install(s) (e.g., add yarn add for "chat" and "@chat-adapter/state-memory") or
change the example imports to only use packages you explicitly instruct the
reader to install; reference the sample import names ("chat",
"@chat-adapter/state-memory") and the existing install line ("yarn add
`@beeper/chat-adapter-desktop-api`") so the consumer instructions and sample code
match.
In `@packages/chat-adapter/src/beeper-desktop-api.d.ts`:
- Around line 1-13: The ambient module declaration for '@beeper/desktop-api'
collapses SDK types to any and forces a required baseURL; remove this file so
the project uses the SDK's published types, or replace it with a minimal module
augmentation that only fixes what the SDK misses (e.g., make constructor option
baseURL optional and augment types for BeeperDesktop.messages/accounts/chats and
get<T>() to return APIPromise<T>), targeting the module name
'@beeper/desktop-api' and the BeeperDesktop class and get<T>() signature so
TypeScript picks up the real APIPromise<Rsp> and typed resources instead of any.
In `@packages/chat-adapter/src/index.ts`:
- Line 52: reactionStateByMessage is an unbounded Map that will grow
indefinitely; replace it with a bounded cache (e.g., an LRU cache like lru-cache
or a custom bounded Map with eviction) or add TTL+periodic cleanup so entries
are pruned when capacity or age limits are exceeded. Change the field
declaration (reactionStateByMessage) to the cache instance and update all places
that call .get/.set/.has to use the cache API (or wrap accessors) so evictions
happen automatically; apply the same bounded-cache/TTL cleanup approach to any
other long-lived per-message state maps in this file to prevent memory leaks.
- Around line 225-239: The catch block in the method that calls
this.client.get(`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`)
is swallowing all errors; update the catch to capture the error (e.g., catch
(err)) and emit a log via the existing logger or processLogger including context
(chatID, messageID, accountID) before proceeding to the fallback
this.client.messages.search call, then continue returning
toChatMessage(found...) as before.
- Around line 331-340: The decodeThreadId function mishandles chat IDs
containing additional colons because it uses a plain split(":"), which truncates
the chatID; update decodeThreadId to only treat the first two colon separations
as prefix and accountID and treat the rest as the chatID (e.g., split into
parts, take parts[0] as prefix, parts[1] as accountID, and join the remaining
parts with ":" for chatID), then validate prefix === "beeperDesktopAPI" and that
accountID and chatID exist, and finally return accountID and chatID after
decodeURIComponent; reference decodeThreadId (and ensure compatibility with
encodeThreadId which uses encodeURIComponent).
---
Nitpick comments:
In `@packages/chat-adapter/src/index.ts`:
- Around line 703-711: The factory function createBeeperDesktopAPIAdapter
spreads config into the new BeeperDesktopAPIAdapter but then redundantly sets
userName: config.userName; remove the redundant property so the returned object
simply spreads ...config (and still sets logger) to avoid duplication and keep
behavior unchanged—update the createBeeperDesktopAPIAdapter function to
eliminate the explicit userName assignment.
- Around line 113-138: The WebSocket connection created in the constructor
(this.ws = new WebSocket(wsURL, { ... })) has no connection timeout and can
hang; update the WebSocket instantiation to include a handshake timeout (e.g.,
pass handshakeTimeout) and handle timeout errors by listening for the "error" or
"close" events to trigger scheduleReconnect (respecting manualStopWS and
autoConnectEvents) and set this.ws = null; ensure the timeout value is
configurable or reasonable and that logging via this.logger.error/ warn records
timeout-specific info so handleWSEvent, scheduleReconnect, manualStopWS and
autoConnectEvents flow remains correct.
- Around line 175-180: deleteMessage currently hardcodes forEveryone: false
which prevents callers from deleting messages for all participants; change the
deleteMessage signature to accept an optional parameter (e.g., options? or
forEveryone?: boolean) with a default of false, update callers and tests as
needed, and pass that value into the request body instead of the literal false
so the function (and methods that call decodeThreadId and this.client.delete)
can request deletion for everyone when desired.
- Around line 420-429: scheduleReconnect currently always uses
wsReconnectDelayMs fixed delay; change it to exponential backoff by adding a
retry state (e.g., reconnectAttempts or currentReconnectDelay) and a max cap
(e.g., maxWsReconnectDelayMs). Compute delay = min(wsReconnectDelayMs * 2 **
reconnectAttempts, maxWsReconnectDelayMs) when scheduling, increment
reconnectAttempts after scheduling, and keep existing guard on reconnectTimer.
In the startEventsListener success path reset
reconnectAttempts/currentReconnectDelay to initial value so future failures
start from the base delay; preserve existing symbols (scheduleReconnect,
reconnectTimer, wsReconnectDelayMs, startEventsListener, wsSubscriptions,
logger.error) when implementing.
In `@packages/chat-adapter/src/types.ts`:
- Around line 5-6: The public config exposes two ambiguous keys botUserID and
botUserId; pick a single canonical key (choose one spelling, e.g., botUserId) in
the types.ts declaration and remove the duplicate to avoid ambiguity, then
update the runtime normalization logic (e.g., the config parsing/initialization
path that reads external config) to accept the deprecated alias if present by
mapping botUserID -> botUserId (or vice versa) and explicitly document the
precedence (alias value is used only if the canonical key is missing). Ensure
any references to both symbols in code, tests, and docs are updated to use the
canonical name and add a short deprecation comment where the alias mapping is
handled.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dbf9e16d-49e0-455f-890c-9a58ea4f8930
⛔ Files ignored due to path filters (1)
packages/chat-adapter/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (11)
packages/chat-adapter/.gitignorepackages/chat-adapter/README.mdpackages/chat-adapter/examples/logger/index.mjspackages/chat-adapter/package.jsonpackages/chat-adapter/scripts/prepare-dist.mjspackages/chat-adapter/src/beeper-desktop-api.d.tspackages/chat-adapter/src/index.tspackages/chat-adapter/src/types.tspackages/chat-adapter/tsconfig.jsonpackages/chat-adapter/tsup.config.tspackages/chat-adapter/vitest.config.ts
📜 Review details
🧰 Additional context used
🧬 Code graph analysis (3)
packages/chat-adapter/scripts/prepare-dist.mjs (1)
scripts/utils/make-dist-package-json.cjs (1)
value(5-5)
packages/chat-adapter/src/types.ts (1)
packages/chat-adapter/src/index.ts (3)
BeeperDesktopAPIAdapterConfig(714-714)BeeperDesktopAPIServerInfo(715-715)BeeperDesktopAPIThreadID(716-716)
packages/chat-adapter/examples/logger/index.mjs (1)
packages/chat-adapter/src/index.ts (1)
createBeeperDesktopAPIAdapter(703-711)
🪛 ESLint
packages/chat-adapter/tsup.config.ts
[error] 1-1: Replace "tsup" with 'tsup'
(prettier/prettier)
[error] 4-4: Replace "src/index.ts" with 'src/index.ts'
(prettier/prettier)
[error] 5-5: Replace "esm" with 'esm'
(prettier/prettier)
[error] 9-9: Replace "ws" with 'ws'
(prettier/prettier)
packages/chat-adapter/src/index.ts
[error] 1-1: Replace "@beeper/desktop-api" with '@beeper/desktop-api'
(prettier/prettier)
[error] 2-2: Replace "@chat-adapter/shared" with '@chat-adapter/shared'
(prettier/prettier)
[error] 19-19: Replace "chat" with 'chat'
(prettier/prettier)
[error] 20-20: Replace "chat" with 'chat'
(prettier/prettier)
[error] 21-21: Replace "ws" with 'ws'
(prettier/prettier)
[error] 28-28: Replace "./types" with './types'
(prettier/prettier)
[error] 31-31: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'
(prettier/prettier)
[error] 49-49: Replace "*" with '*'
(prettier/prettier)
[error] 53-53: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 56-56: Replace "http://localhost:23373").replace(/\/+$/,·"" with 'http://localhost:23373').replace(/\/+$/,·''
(prettier/prettier)
[error] 63-63: Replace "bot" with 'bot'
(prettier/prettier)
[error] 74-74: Replace "BeeperDesktopAPI·adapter·initialized" with 'BeeperDesktopAPI·adapter·initialized'
(prettier/prettier)
[error] 88-88: Replace "BeeperDesktopAPI·uses·WebSocket·events·(/v1/ws),·not·inbound·webhooks.·Call·startEventsListener()." with 'BeeperDesktopAPI·uses·WebSocket·events·(/v1/ws),·not·inbound·webhooks.·Call·startEventsListener().'
(prettier/prettier)
[error] 97-97: Replace "*" with '*'
(prettier/prettier)
[error] 99-99: Replace "Chat·instance·not·initialized" with 'Chat·instance·not·initialized'
(prettier/prettier)
[error] 112-112: Replace "ws" with 'ws'
(prettier/prettier)
[error] 119-119: Replace "open" with 'open'
(prettier/prettier)
[error] 120-120: Replace "BeeperDesktopAPI·WS·connected" with 'BeeperDesktopAPI·WS·connected'
(prettier/prettier)
[error] 121-121: Replace "subscriptions.set" with 'subscriptions.set'
(prettier/prettier)
[error] 124-124: Replace "message" with 'message'
(prettier/prettier)
[error] 128-128: Replace "error" with 'error'
(prettier/prettier)
[error] 129-129: Replace "BeeperDesktopAPI·WS·error" with 'BeeperDesktopAPI·WS·error'
(prettier/prettier)
[error] 132-132: Replace "close" with 'close'
(prettier/prettier)
[error] 133-133: Replace "BeeperDesktopAPI·WS·disconnected" with 'BeeperDesktopAPI·WS·disconnected'
(prettier/prettier)
[error] 164-164: Replace threadID:·string,·messageID:·string,·message:·AdapterPostableMessage with ⏎····threadID:·string,⏎····messageID:·string,⏎····message:·AdapterPostableMessage,⏎··
(prettier/prettier)
[error] 168-168: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,⏎·····`
(prettier/prettier)
[error] 169-169: Insert ··
(prettier/prettier)
[error] 170-170: Replace } with ··},⏎····
(prettier/prettier)
[error] 177-177: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,⏎·····`
(prettier/prettier)
[error] 178-178: Insert ··
(prettier/prettier)
[error] 179-179: Replace } with ··},⏎····
(prettier/prettier)
[error] 184-184: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions`,⏎·····`
(prettier/prettier)
[error] 185-185: Insert ··
(prettier/prettier)
[error] 186-186: Replace } with ··},⏎····
(prettier/prettier)
[error] 191-191: Replace ``/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions, with `⏎······`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}/reactions`,⏎·····`
(prettier/prettier)
[error] 192-192: Insert ··
(prettier/prettier)
[error] 193-193: Replace } with ··},⏎····
(prettier/prettier)
[error] 205-205: Replace "backward" with 'backward'
(prettier/prettier)
[error] 211-211: Replace "forward"·?·"after"·:·"before" with 'forward'·?·'after'·:·'before'
(prettier/prettier)
[error] 217-217: Replace ·this.toChatMessage(item·as·unknown·as·BeeperDesktopAPIMessage,·accountID,·threadID) with ⏎··········this.toChatMessage(item·as·unknown·as·BeeperDesktopAPIMessage,·accountID,·threadID),⏎········
(prettier/prettier)
[error] 218-218: Replace "forward"·?·(data.newestCursor·??·undefined)·:·(data.oldestCursor·??·undefined) with 'forward'·?·data.newestCursor·??·undefined·:·data.oldestCursor·??·undefined
(prettier/prettier)
[error] 249-249: Replace "single" with 'single'
(prettier/prettier)
[error] 275-275: Replace channelId:·string,·options:·ListThreadsOptions·=·{} with ⏎····channelId:·string,⏎····options:·ListThreadsOptions·=·{},⏎··
(prettier/prettier)
[error] 280-280: Replace "before" with 'before'
(prettier/prettier)
[error] 283-283: Replace "any" with 'any'
(prettier/prettier)
[error] 294-294: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 295-295: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 297-297: Replace "" with ''
(prettier/prettier)
[error] 322-322: Replace "single" with 'single'
(prettier/prettier)
[error] 328-328: Replace platformData.chatID with ⏎······platformData.chatID,⏎····
(prettier/prettier)
[error] 332-332: Replace ":" with ':'
(prettier/prettier)
[error] 333-333: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'
(prettier/prettier)
[error] 362-362: Replace "Ignoring·invalid·BeeperDesktopAPI·WS·payload" with 'Ignoring·invalid·BeeperDesktopAPI·WS·payload'
(prettier/prettier)
[error] 366-366: Replace "message.upserted" with 'message.upserted'
(prettier/prettier)
[error] 383-383: Replace "reaction.added"·||·event.type·===·"reaction.removed" with 'reaction.added'·||·event.type·===·'reaction.removed'
(prettier/prettier)
[error] 397-397: Replace "reaction.added" with 'reaction.added'
(prettier/prettier)
[error] 406-406: Replace "reaction.added" with 'reaction.added'
(prettier/prettier)
[error] 411-411: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 425-425: Replace "BeeperDesktopAPI·WS·reconnect·failed" with 'BeeperDesktopAPI·WS·reconnect·failed'
(prettier/prettier)
[error] 432-432: Replace ":" with ':'
(prettier/prettier)
[error] 433-433: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'
(prettier/prettier)
[error] 440-440: Replace ":" with ':'
(prettier/prettier)
[error] 466-466: Replace "/v1/info" with '/v1/info'
(prettier/prettier)
[error] 469-469: Replace "info-endpoint" with 'info-endpoint'
(prettier/prettier)
[error] 470-470: Replace "version",·"desktopVersion",·"appVersion" with 'version',·'desktopVersion',·'appVersion'
(prettier/prettier)
[error] 471-471: Replace "apiVersion",·"desktopAPIVersion",·"specVersion" with 'apiVersion',·'desktopAPIVersion',·'specVersion'
(prettier/prettier)
[error] 483-483: Replace "headers" with 'headers'
(prettier/prettier)
[error] 487-487: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 494-494: Replace "string" with 'string'
(prettier/prettier)
[error] 500-505: Replace ⏎······"x-beeper-desktop-api-version",⏎······"x-beeper-desktop-version",⏎······"x-api-version",⏎······"server",⏎···· with 'x-beeper-desktop-api-version',·'x-beeper-desktop-version',·'x-api-version',·'server'
(prettier/prettier)
[error] 518-518: Replace "messageID",·"messageId" with 'messageID',·'messageId'
(prettier/prettier)
[error] 519-519: Replace "reactionKey",·"reaction",·"description" with 'reactionKey',·'reaction',·'description'
(prettier/prettier)
[error] 520-520: Replace "participantID",·"senderID",·"userID"])·??·"unknown" with 'participantID',·'senderID',·'userID'])·??·'unknown'
(prettier/prettier)
[error] 528-528: Replace "string" with 'string'
(prettier/prettier)
[error] 534-534: Replace "string" with 'string'
(prettier/prettier)
[error] 542-542: Replace "string" with 'string'
(prettier/prettier)
[error] 564-564: Replace "" with ''
(prettier/prettier)
[error] 566-566: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 595-595: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 604-604: Replace "unknown",·reactionKey·=·""]·=·identity.split("::" with 'unknown',·reactionKey·=·'']·=·identity.split('::'
(prettier/prettier)
[error] 618-618: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 644-644: Replace message:·BeeperDesktopAPIMessage,·accountID:·string,·threadID:·string with ⏎····message:·BeeperDesktopAPIMessage,⏎····accountID:·string,⏎····threadID:·string,⏎··
(prettier/prettier)
[error] 645-645: Replace "" with ''
(prettier/prettier)
[error] 647-649: Replace ·this.botUserID⏎······?·mentions.includes(this.botUserID)·||·mentions.includes("@room")⏎····· with ⏎······this.botUserID·?·mentions.includes(this.botUserID)·||·mentions.includes('@room')
(prettier/prettier)
[error] 661-661: Replace "unknown" with 'unknown'
(prettier/prettier)
[error] 677-677: Insert ,
(prettier/prettier)
[error] 681-681: Replace "image"·|·"video"·|·"audio"·|·"file" with 'image'·|·'video'·|·'audio'·|·'file'
(prettier/prettier)
[error] 682-682: Replace "file" with 'file'
(prettier/prettier)
[error] 684-684: Replace "IMAGE"·||·normalized·===·"STICKER"·||·normalized·===·"IMG")·return·"image" with 'IMAGE'·||·normalized·===·'STICKER'·||·normalized·===·'IMG')·return·'image'
(prettier/prettier)
[error] 685-685: Replace "VIDEO")·return·"video" with 'VIDEO')·return·'video'
(prettier/prettier)
[error] 686-686: Replace "AUDIO"·||·normalized·===·"VOICE")·return·"audio" with 'AUDIO'·||·normalized·===·'VOICE')·return·'audio'
(prettier/prettier)
[error] 687-687: Replace "file" with 'file'
(prettier/prettier)
[error] 694-694: Replace "string" with 'string'
(prettier/prettier)
[error] 695-695: Replace "raw" with 'raw'
(prettier/prettier)
[error] 696-696: Replace "markdown" with 'markdown'
(prettier/prettier)
[error] 697-697: Replace "ast" with 'ast'
(prettier/prettier)
[error] 699-699: Replace "" with ''
(prettier/prettier)
[error] 708-708: Replace "info",·"beeper-desktop-api-adapter" with 'info',·'beeper-desktop-api-adapter'
(prettier/prettier)
[error] 717-717: Replace "./types" with './types'
(prettier/prettier)
packages/chat-adapter/scripts/prepare-dist.mjs
[error] 53-53: Replace path.join(packageDir,·'examples',·'logger',·'index.mjs'),·path.join(distDir,·'examples',·'logger',·'index.mjs') with ⏎··path.join(packageDir,·'examples',·'logger',·'index.mjs'),⏎··path.join(distDir,·'examples',·'logger',·'index.mjs'),⏎
(prettier/prettier)
packages/chat-adapter/src/types.ts
[error] 14-14: Replace "info-endpoint"·|·"headers"·|·"unknown" with 'info-endpoint'·|·'headers'·|·'unknown'
(prettier/prettier)
[error] 54-54: Replace "ready" with 'ready'
(prettier/prettier)
[error] 55-55: Replace "subscriptions.updated" with 'subscriptions.updated'
(prettier/prettier)
[error] 56-56: Replace "error" with 'error'
(prettier/prettier)
[error] 57-57: Replace "chat.upserted" with 'chat.upserted'
(prettier/prettier)
[error] 58-58: Replace "chat.deleted" with 'chat.deleted'
(prettier/prettier)
[error] 59-59: Replace "message.upserted" with 'message.upserted'
(prettier/prettier)
[error] 60-60: Replace "message.deleted" with 'message.deleted'
(prettier/prettier)
[error] 61-61: Replace "reaction.added" with 'reaction.added'
(prettier/prettier)
[error] 62-62: Replace "reaction.removed" with 'reaction.removed'
(prettier/prettier)
packages/chat-adapter/vitest.config.ts
[error] 1-1: Replace "vitest/config" with 'vitest/config'
(prettier/prettier)
[error] 6-6: Replace "node" with 'node'
(prettier/prettier)
[error] 9-9: Replace "v8" with 'v8'
(prettier/prettier)
[error] 10-10: Replace "text",·"json-summary" with 'text',·'json-summary'
(prettier/prettier)
[error] 11-11: Replace "src/**/*.ts" with 'src/**/*.ts'
(prettier/prettier)
[error] 12-12: Replace "src/**/*.test.ts" with 'src/**/*.test.ts'
(prettier/prettier)
packages/chat-adapter/examples/logger/index.mjs
[error] 1-1: Replace "chat" with 'chat'
(prettier/prettier)
[error] 2-2: Replace "@chat-adapter/state-memory" with '@chat-adapter/state-memory'
(prettier/prettier)
[error] 3-3: Replace "../../dist/index.js" with '../../dist/index.js'
(prettier/prettier)
[error] 7-7: Replace "Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN" with 'Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN'
(prettier/prettier)
[error] 10-10: Replace "http://localhost:23373" with 'http://localhost:23373'
(prettier/prettier)
[error] 13-13: Replace "*" with '*'
(prettier/prettier)
[error] 14-14: Replace "," with ','
(prettier/prettier)
[error] 24-24: Replace "bot" with 'bot'
(prettier/prettier)
[error] 28-28: Replace "bot" with 'bot'
(prettier/prettier)
[error] 33-33: Replace "info" with 'info'
(prettier/prettier)
[error] 37-37: Replace "[mention]" with '[mention]'
(prettier/prettier)
[error] 47-47: Replace "[new-message]" with '[new-message]'
(prettier/prettier)
[error] 57-57: Replace "[subscribed-message]" with '[subscribed-message]'
(prettier/prettier)
[error] 66-66: Replace "[reaction]" with '[reaction]'
(prettier/prettier)
[error] 76-76: Replace "BeeperDesktopAPI·server·info" with 'BeeperDesktopAPI·server·info'
(prettier/prettier)
[error] 79-79: Replace "BeeperDesktopAPI·logger·is·running" with 'BeeperDesktopAPI·logger·is·running'
(prettier/prettier)
🪛 GitHub Check: lint
packages/chat-adapter/examples/logger/index.mjs
[failure] 33-33:
Replace "info" with 'info'
[failure] 28-28:
Replace "bot" with 'bot'
[failure] 24-24:
Replace "bot" with 'bot'
[failure] 14-14:
Replace "," with ','
[failure] 13-13:
Replace "*" with '*'
[failure] 10-10:
Replace "http://localhost:23373" with 'http://localhost:23373'
[failure] 7-7:
Replace "Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN" with 'Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN'
[failure] 3-3:
Replace "../../dist/index.js" with '../../dist/index.js'
[failure] 2-2:
Replace "@chat-adapter/state-memory" with '@chat-adapter/state-memory'
[failure] 1-1:
Replace "chat" with 'chat'
🔇 Additional comments (9)
packages/chat-adapter/package.json (1)
30-34: The npm registry shows the latest published version of@beeper/desktop-apiis 0.1.4, not 4.4.1 as stated in the review. The declared version^4.2.3in package.json is valid. The suggestion to bump to^4.4.1is not supported by the actual latest published release.> Likely an incorrect or invalid review comment.packages/chat-adapter/src/index.ts (3)
30-84: LGTM!The adapter initialization is well-structured with sensible defaults, proper logger injection, and flexible configuration handling (including both
botUserIDandbotUserIdvariants for convenience).
355-418: LGTM!The WebSocket event handling correctly processes message upserts with reaction diffing and explicit reaction events. The guard clauses properly validate required fields before processing.
644-679: LGTM!The message conversion logic correctly maps all fields including author information, attachments with type normalization, and mention detection.
packages/chat-adapter/examples/logger/index.mjs (2)
5-34: LGTM!The configuration handling correctly validates the required access token, provides sensible defaults, and properly parses the comma-separated chat IDs.
36-84: LGTM!The example demonstrates a complete integration flow: registering event handlers, initializing the bot, starting the events listener, and keeping the process alive. The handlers appropriately log events for debugging purposes.
packages/chat-adapter/scripts/prepare-dist.mjs (3)
1-12: LGTM!The script correctly sets up paths using ESM patterns and validates the dist directory existence before proceeding.
49-54: LGTM!The file operations correctly write the transformed package.json and copy necessary assets to the dist directory. Using synchronous operations is appropriate for a build script.
30-47: No action needed. The sourcepackage.jsondoes not containpeerDependencies, so thepublishPackageobject correctly excludes them.
| ## Install | ||
|
|
||
| ```bash | ||
| yarn add @beeper/chat-adapter-desktop-api |
There was a problem hiding this comment.
The quickstart omits packages required by the sample.
The snippet imports chat and @chat-adapter/state-memory, but the install section only adds @beeper/chat-adapter-desktop-api. @chat-adapter/state-memory is only a devDependency in this package, so the README cannot work as written for consumers. Either list the extra installs here or rewrite the sample around packages the reader is explicitly told to add.
📦 Suggested README fix
-yarn add `@beeper/chat-adapter-desktop-api`
+yarn add `@beeper/chat-adapter-desktop-api` chat `@chat-adapter/state-memory`Also applies to: 14-16
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chat-adapter/README.md` at line 8, The quickstart install
instruction only adds `@beeper/chat-adapter-desktop-api` but the sample imports
"chat" and "@chat-adapter/state-memory"; update the README to either include the
missing install(s) (e.g., add yarn add for "chat" and
"@chat-adapter/state-memory") or change the example imports to only use packages
you explicitly instruct the reader to install; reference the sample import names
("chat", "@chat-adapter/state-memory") and the existing install line ("yarn add
`@beeper/chat-adapter-desktop-api`") so the consumer instructions and sample code
match.
| declare module '@beeper/desktop-api' { | ||
| export default class BeeperDesktop { | ||
| constructor(options: { accessToken: string; baseURL: string }); | ||
|
|
||
| messages: any; | ||
| accounts: any; | ||
| chats: any; | ||
|
|
||
| get<T = unknown>(path: string, options?: unknown): any; | ||
| put(path: string, options?: unknown): Promise<unknown>; | ||
| post(path: string, options?: unknown): Promise<unknown>; | ||
| delete(path: string, options?: unknown): Promise<unknown>; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python - <<'PY'
import io, json, pathlib, tarfile, urllib.request
meta = json.load(urllib.request.urlopen('https://registry.npmjs.org/%40beeper%2Fdesktop-api'))
latest = meta['dist-tags']['latest']
version = meta['versions'][latest]
print('latest:', latest)
print('types field:', version.get('types') or version.get('typings'))
tarball = version['dist']['tarball']
data = urllib.request.urlopen(tarball).read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as tar:
dts = sorted(name for name in tar.getnames() if name.endswith('.d.ts'))
print('published .d.ts sample:')
for name in dts[:20]:
print(name)
print('\nlocal any-typed members:')
for line in pathlib.Path('packages/chat-adapter/src/beeper-desktop-api.d.ts').read_text().splitlines():
if ': any' in line or '): any' in line:
print(line)
PYRepository: beeper/desktop-api-js
Length of output: 2646
🏁 Script executed:
#!/bin/bash
# Check if the file exists and read its content
echo "=== Checking if the file exists ==="
if [ -f "packages/chat-adapter/src/beeper-desktop-api.d.ts" ]; then
echo "File exists. Content:"
cat -n "packages/chat-adapter/src/beeper-desktop-api.d.ts"
else
echo "File not found. Listing packages/chat-adapter/src directory:"
ls -la packages/chat-adapter/src/ 2>/dev/null || echo "Directory does not exist"
fi
echo -e "\n=== Checking for `@beeper/desktop-api` in package.json ==="
find packages -name "package.json" -type f | while read pkgjson; do
if grep -q "@beeper/desktop-api" "$pkgjson"; then
echo "Found in: $pkgjson"
grep "@beeper/desktop-api" "$pkgjson"
fi
doneRepository: beeper/desktop-api-js
Length of output: 1018
🏁 Script executed:
#!/bin/bash
# Search for imports/usage of `@beeper/desktop-api` in the codebase
echo "=== Searching for imports of `@beeper/desktop-api` ==="
rg "from\s+['\"]@beeper/desktop-api" --type ts --type tsx --type js --type jsx -A 2Repository: beeper/desktop-api-js
Length of output: 162
🏁 Script executed:
#!/bin/bash
# Check if there are any other type declaration files or references
echo "=== Looking for references to BeeperDesktop class ==="
rg "BeeperDesktop" --type ts --type tsx --type js --type jsx -A 1 -B 1 | head -50Repository: beeper/desktop-api-js
Length of output: 163
🏁 Script executed:
#!/bin/bash
# Try simpler search without file type specifiers
echo "=== Searching for imports of `@beeper/desktop-api` ==="
rg "from.*@beeper/desktop-api|import.*@beeper/desktop-api" -A 2
echo -e "\n=== Searching for BeeperDesktop usage ==="
rg "BeeperDesktop" -A 1 -B 1Repository: beeper/desktop-api-js
Length of output: 47448
🏁 Script executed:
#!/bin/bash
# Check if there are other SDK type files in node_modules or dist
echo "=== Checking for actual SDK types in dist/node_modules ==="
find . -path ./node_modules -prune -o -name "@beeper" -type d -print 2>/dev/null | head -20
echo -e "\n=== Looking at package.json to understand the dev setup ==="
cat packages/chat-adapter/package.json | grep -A 5 -B 5 "@beeper/desktop-api"Repository: beeper/desktop-api-js
Length of output: 486
🏁 Script executed:
#!/bin/bash
# Verify the actual SDK typing by checking src/client.ts constructor and exports
echo "=== Actual SDK constructor from src/client.ts ==="
grep -A 10 "constructor(" src/client.ts | head -20
echo -e "\n=== SDK exports typed resources ==="
grep -E "BeeperDesktop\.(Accounts|Chats|Messages|Assets)" src/client.ts | head -10
echo -e "\n=== Verify get method signature in SDK ==="
rg "^\s+get<" src/ -A 2 | head -15Repository: beeper/desktop-api-js
Length of output: 979
🏁 Script executed:
#!/bin/bash
# Check how the shim actually impacts type usage in chat-adapter
echo "=== How chat-adapter uses BeeperDesktop from the shim ==="
grep -A 5 "new BeeperDesktop" packages/chat-adapter/src/index.ts
echo -e "\n=== Type annotations in chat-adapter that rely on SDK types ==="
grep "BeeperDesktopAPI" packages/chat-adapter/src/index.ts | head -5Repository: beeper/desktop-api-js
Length of output: 680
Remove this ambient module declaration and use the SDK's published types.
The official @beeper/desktop-api SDK has fully typed APIs: get<Rsp>() returns APIPromise<Rsp>, not any; and provides typed resources like BeeperDesktop.Accounts, BeeperDesktop.Chats, etc. This shim collapses all of that to any and incorrectly requires baseURL in the constructor (the actual SDK makes it optional with environment variable fallback). This defeats TypeScript's type checking—for instance, client.get<BeeperDesktopAPIMessage>(...) returns any instead of APIPromise<BeeperDesktopAPIMessage>.
Either remove the shim and rely on the SDK's published declarations, or use module augmentation to extend only the missing pieces instead of redeclaring the whole module.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chat-adapter/src/beeper-desktop-api.d.ts` around lines 1 - 13, The
ambient module declaration for '@beeper/desktop-api' collapses SDK types to any
and forces a required baseURL; remove this file so the project uses the SDK's
published types, or replace it with a minimal module augmentation that only
fixes what the SDK misses (e.g., make constructor option baseURL optional and
augment types for BeeperDesktop.messages/accounts/chats and get<T>() to return
APIPromise<T>), targeting the module name '@beeper/desktop-api' and the
BeeperDesktop class and get<T>() signature so TypeScript picks up the real
APIPromise<Rsp> and typed resources instead of any.
| private wsSubscriptions: string[] = ["*"]; | ||
| private reconnectTimer: NodeJS.Timeout | null = null; | ||
| private manualStopWS = false; | ||
| private reactionStateByMessage = new Map<string, Set<string>>(); |
There was a problem hiding this comment.
Potential memory leak: reactionStateByMessage grows unbounded.
The reactionStateByMessage Map stores reaction state for every processed message but is never pruned. For long-running adapter instances, this will grow indefinitely, potentially causing memory exhaustion.
Consider implementing an LRU cache or periodic cleanup of old entries.
💡 Suggested approach using a bounded cache
+ private readonly maxReactionCacheSize = 10000;
private processReactionDiffFromMessageUpsert(message: BeeperDesktopAPIMessage): void {
// ... existing logic ...
this.reactionStateByMessage.set(messageKey, next);
+
+ // Prune oldest entries if cache exceeds limit
+ if (this.reactionStateByMessage.size > this.maxReactionCacheSize) {
+ const keysToDelete = Array.from(this.reactionStateByMessage.keys())
+ .slice(0, this.reactionStateByMessage.size - this.maxReactionCacheSize);
+ for (const key of keysToDelete) {
+ this.reactionStateByMessage.delete(key);
+ }
+ }
}Also applies to: 549-626
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chat-adapter/src/index.ts` at line 52, reactionStateByMessage is an
unbounded Map that will grow indefinitely; replace it with a bounded cache
(e.g., an LRU cache like lru-cache or a custom bounded Map with eviction) or add
TTL+periodic cleanup so entries are pruned when capacity or age limits are
exceeded. Change the field declaration (reactionStateByMessage) to the cache
instance and update all places that call .get/.set/.has to use the cache API (or
wrap accessors) so evictions happen automatically; apply the same
bounded-cache/TTL cleanup approach to any other long-lived per-message state
maps in this file to prevent memory leaks.
| try { | ||
| const message = await this.client.get<BeeperDesktopAPIMessage>( | ||
| `/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`, | ||
| ); | ||
| return this.toChatMessage(message, accountID, threadID); | ||
| } catch { | ||
| const data = await this.client.messages.search({ | ||
| chatIDs: [chatID], | ||
| limit: 50, | ||
| }); | ||
| const found = data.items.find((item: any) => item.id === messageID); | ||
| if (!found) return null; | ||
| return this.toChatMessage(found as unknown as BeeperDesktopAPIMessage, accountID, threadID); | ||
| } | ||
| } |
There was a problem hiding this comment.
Silent error handling may mask issues.
The bare catch block on line 230 swallows all errors from the direct message fetch, including potential network issues or authentication failures. Consider logging the error before falling back to search.
🔧 Proposed fix
try {
const message = await this.client.get<BeeperDesktopAPIMessage>(
`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`,
);
return this.toChatMessage(message, accountID, threadID);
- } catch {
+ } catch (error) {
+ this.logger.debug("Direct message fetch failed, falling back to search", { messageID, error: String(error) });
const data = await this.client.messages.search({📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| const message = await this.client.get<BeeperDesktopAPIMessage>( | |
| `/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`, | |
| ); | |
| return this.toChatMessage(message, accountID, threadID); | |
| } catch { | |
| const data = await this.client.messages.search({ | |
| chatIDs: [chatID], | |
| limit: 50, | |
| }); | |
| const found = data.items.find((item: any) => item.id === messageID); | |
| if (!found) return null; | |
| return this.toChatMessage(found as unknown as BeeperDesktopAPIMessage, accountID, threadID); | |
| } | |
| } | |
| try { | |
| const message = await this.client.get<BeeperDesktopAPIMessage>( | |
| `/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`, | |
| ); | |
| return this.toChatMessage(message, accountID, threadID); | |
| } catch (error) { | |
| this.logger.debug("Direct message fetch failed, falling back to search", { messageID, error: String(error) }); | |
| const data = await this.client.messages.search({ | |
| chatIDs: [chatID], | |
| limit: 50, | |
| }); | |
| const found = data.items.find((item: any) => item.id === messageID); | |
| if (!found) return null; | |
| return this.toChatMessage(found as unknown as BeeperDesktopAPIMessage, accountID, threadID); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chat-adapter/src/index.ts` around lines 225 - 239, The catch block
in the method that calls
this.client.get(`/v1/chats/${encodeURIComponent(chatID)}/messages/${encodeURIComponent(messageID)}`)
is swallowing all errors; update the catch to capture the error (e.g., catch
(err)) and emit a log via the existing logger or processLogger including context
(chatID, messageID, accountID) before proceeding to the fallback
this.client.messages.search call, then continue returning
toChatMessage(found...) as before.
| decodeThreadId(threadID: string): BeeperDesktopAPIThreadID { | ||
| const [prefix, accountID, chatID] = threadID.split(":"); | ||
| if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) { | ||
| throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`); | ||
| } | ||
| return { | ||
| accountID: decodeURIComponent(accountID), | ||
| chatID: decodeURIComponent(chatID), | ||
| }; | ||
| } |
There was a problem hiding this comment.
Bug: decodeThreadId doesn't handle colons in chatID correctly.
The split(":") on line 332 will incorrectly truncate chatID if it contains colon characters. For example, "beeperDesktopAPI:acc1:chat%3Awith%3Acolons" would only capture "chat%3Awith%3Acolons" as chatID before decoding, but if chatID was encoded as "beeperDesktopAPI:acc1:chat:id" (without encoding), only "chat" would be captured.
Since encodeThreadId uses encodeURIComponent, colons should be encoded, but to be defensive against malformed IDs:
🔧 Safer implementation using limited split
decodeThreadId(threadID: string): BeeperDesktopAPIThreadID {
- const [prefix, accountID, chatID] = threadID.split(":");
+ const parts = threadID.split(":");
+ const prefix = parts[0];
+ const accountID = parts[1];
+ const chatID = parts.slice(2).join(":");
if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) {
throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| decodeThreadId(threadID: string): BeeperDesktopAPIThreadID { | |
| const [prefix, accountID, chatID] = threadID.split(":"); | |
| if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) { | |
| throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`); | |
| } | |
| return { | |
| accountID: decodeURIComponent(accountID), | |
| chatID: decodeURIComponent(chatID), | |
| }; | |
| } | |
| decodeThreadId(threadID: string): BeeperDesktopAPIThreadID { | |
| const parts = threadID.split(":"); | |
| const prefix = parts[0]; | |
| const accountID = parts[1]; | |
| const chatID = parts.slice(2).join(":"); | |
| if (!(prefix === "beeperDesktopAPI" && accountID && chatID)) { | |
| throw new Error(`Invalid BeeperDesktopAPI thread ID: ${threadID}`); | |
| } | |
| return { | |
| accountID: decodeURIComponent(accountID), | |
| chatID: decodeURIComponent(chatID), | |
| }; | |
| } |
🧰 Tools
🪛 ESLint
[error] 332-332: Replace ":" with ':'
(prettier/prettier)
[error] 333-333: Replace "beeperDesktopAPI" with 'beeperDesktopAPI'
(prettier/prettier)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chat-adapter/src/index.ts` around lines 331 - 340, The
decodeThreadId function mishandles chat IDs containing additional colons because
it uses a plain split(":"), which truncates the chatID; update decodeThreadId to
only treat the first two colon separations as prefix and accountID and treat the
rest as the chatID (e.g., split into parts, take parts[0] as prefix, parts[1] as
accountID, and join the remaining parts with ":" for chatID), then validate
prefix === "beeperDesktopAPI" and that accountID and chatID exist, and finally
return accountID and chatID after decodeURIComponent; reference decodeThreadId
(and ensure compatibility with encodeThreadId which uses encodeURIComponent).
Summary
@beeper/chat-adapter-desktop-apiunderpackages/chat-adapter@beeper/desktop-apirelease in both source and packageddistmetadataTesting
yarn typecheckyarn buildyarn test