Commit Graph

508 Commits

Author SHA1 Message Date
github-actions[bot] e3c136b6dc chore(release): version packages (#710)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @chat-adapter/discord@4.35.0

### Minor Changes

- 26c0522: Add an opt-in channel allowlist for treating non-bot Discord
messages as directed to the bot without requiring a mention. Configure
via `respondToChannelIds` or the `DISCORD_RESPOND_TO_CHANNEL_IDS` env
var (comma-separated).

### Patch Changes

- b605cf6: Preserve Discord's angle-bracket syntax for suppressing link
previews when rendering markdown.
- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/slack@4.35.0

### Minor Changes

- bb7cd12: Expose sender email addresses on normalized incoming Slack
message authors. `message.author.email` is populated from the same
cached `users.info` lookup used for display names and requires the
`users:read.email` scope; without it the field stays undefined.
- 907450d: Enterprise Grid fixes:

- `handleOAuthCallback` now handles org-wide installs
(`is_enterprise_install`): Slack returns `team: null` for these, and the
installation is now keyed by the enterprise ID — the same key webhook
token resolution looks up — instead of failing. The result includes
`enterpriseId` and `isEnterpriseInstall`, and `SlackInstallation`
records both.
- Socket mode now resolves per-installation tokens for events, slash
commands, and interactive payloads in multi-workspace deployments
(matching the HTTP webhook path), and no longer drops `enterprise_id` /
`is_enterprise_install` / `is_ext_shared_channel` from event payloads.
- The user profile cache and display-name mention reverse index are now
scoped by installation in multi-workspace deployments, so profiles
fetched with one workspace's token no longer bleed into another and
mentions can no longer resolve to a same-named user from a different
workspace. Existing cache entries repopulate on first lookup
(single-workspace keys are unchanged). `withBotToken` accepts an
optional `{ installationId }` so proactive/cron posts outside webhook
handling scope these caches too.
- API calls made while handling an event from an org-wide install now
pass the event's `team_id` explicitly, as Slack requires for
workspace-scoped methods (`conversations.list`, `usergroups.*`, …) on
org tokens. When an event carries a `context_team_id` (shared channels
hosted on an "away" workspace), channel-addressed calls echo it back as
`client_context_team_id`.
- Retried event deliveries (`x-slack-retry-num`, socket `retry_num`) are
dropped when the original delivery was already dispatched, using an
`event_id` marker in the state adapter (24-hour TTL). Events whose first
delivery never arrived are still recovered via the retry.
- Bare `@W…` mentions in outgoing messages are now recognized as raw
Enterprise Grid user IDs (previously only `@U…` was), so they render as
real mentions instead of being treated as display names.
- Event token resolution now prefers the envelope's `authorizations[0]`
— Slack's documented location for the event's installation identity —
over the top-level `team_id`/`enterprise_id`, which can name a different
workspace for Slack Connect shared-channel events. Top-level fields
remain as a fallback.

### Patch Changes

- 80def3a: Add optional `isSystem` field to the normalized message
`Author` type to distinguish platform-generated messages from humans and
bots. The Slack adapter now sets `isSystem: true` for messages authored
by Slack's reserved `USLACK` user (e.g. "@user archived the channel"
notifications in DMs), so consumers no longer need to hard-code
Slack-specific user IDs.
- 92530dd: Return a replyable Slack thread ID from `channel.post()` by
using the posted top-level message's timestamp as the thread root.
- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/teams@4.35.0

### Minor Changes

- 5eb8b84: Add support for adding and removing reactions from Microsoft
Teams messages.
- 46681f5: Expose Microsoft Graph email addresses on normalized incoming
Teams message authors. Resolved user profiles are cached in the state
adapter (1 hour, failed lookups 5 minutes) so the lookup doesn't add a
Graph call per message.
- 160140e: Add native Microsoft Teams targeted message support via
`thread.postEphemeral()` and `channel.postEphemeral()`.
- e06b4b6: Add a `token` config option to `TeamsAdapterConfig` for
supplying a custom token factory, forwarded to the Teams SDK's
`AppOptions.token`. This lets bots authenticate on runtimes that can't
reach Azure IMDS (so `federated` managed identity isn't reachable) but
can still mint access tokens through an external mechanism, without
needing a static client secret.

### Patch Changes

- 3895ab3: Fall back to Microsoft Graph's user principal name when a
Teams user has no mail address.
- 93a58af: Show explicitly configured progress as a native Teams DM
status while preserving native streaming.
- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/telegram@4.35.0

### Minor Changes

- 54eea71: Add an optional Telegram user allowlist via `allowedUserIds`
or the comma-separated `TELEGRAM_ALLOWED_USER_IDS` environment variable.

### Patch Changes

- 0701679: Cache the compiled bot-mention regex in `isBotMentioned`
instead of recompiling it per message, and make the protected `sleep`
helper accept an optional `AbortSignal` so `stopPolling()` interrupts
the polling backoff delay immediately.
- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## chat@4.35.0

### Minor Changes

- 4cb7e5d: Add a `chat/workflow` subpath with `requestApproval()`:
durable human-in-the-loop approvals built on Workflow SDK. It posts an
approval card with Approve/Deny buttons, suspends the workflow until a
user decides (or an optional timeout elapses), validates approvers,
finalizes the card with the outcome, and returns `{ approved, timedOut,
user }`. Also exports the `buildApprovalCard` and `buildResolvedCard`
builders. Requires the new optional `workflow` peer dependency.
- 46681f5: Expose Microsoft Graph email addresses on normalized incoming
Teams message authors. Resolved user profiles are cached in the state
adapter (1 hour, failed lookups 5 minutes) so the lookup doesn't add a
Graph call per message.

### Patch Changes

- 80def3a: Add optional `isSystem` field to the normalized message
`Author` type to distinguish platform-generated messages from humans and
bots. The Slack adapter now sets `isSystem: true` for messages authored
by Slack's reserved `USLACK` user (e.g. "@user archived the channel"
notifications in DMs), so consumers no longer need to hard-code
Slack-specific user IDs.
- 93a58af: Show explicitly configured progress as a native Teams DM
status while preserving native streaming.
- 25f3099: `toAiMessages` no longer drops messages that have no text. A
message with an empty text body is now kept when it has links or
attachments the converter can include: images and text files (`text/*`,
JSON, XML, YAML, etc.) with a working `fetchData()`. Messages whose only
attachments are unsupported (video, audio, other file types, or
attachments without `fetchData()`) are still skipped, and
`onUnsupportedAttachment` now fires for video/audio attachments on these
previously filtered messages.

Note: multipart `content` no longer always starts with a text part. When
a kept message had no text, its `content` array contains only attachment
parts.
## @chat-adapter/gchat@4.35.0

### Patch Changes

- 270b1c2: fix(gchat): accept `endpointUrl` as a direct-webhook verifier
and verify each token type correctly

When a Google Chat app's connection setting **Authentication audience**
is set
to **HTTP endpoint URL** — Google's recommended option for HTTP-hosted
apps
not behind Cloud Run IAM, and the only mode available for Workspace
Add-on
Chat apps — incoming tokens are Google OIDC ID tokens whose `aud` is the
endpoint URL rather than the GCP project number. Previously the adapter
only
verified against `googleChatProjectNumber`, so URL-audience tokens
always
failed with 401 Unauthorized. The adapter now accepts `endpointUrl` as a
direct-webhook verifier (including in the constructor's fail-closed
check),
validating the OIDC token's audience plus the Google Chat issuer email
claims
  (`chat@system.gserviceaccount.com`, or the
`service-{projectNumber}@gcp-sa-gsuiteaddons.iam.gserviceaccount.com`
service
identity for Workspace Add-on Chat apps) with `email_verified: true` — a
public endpoint URL audience alone is not sufficient to forge a request.

  Project-number-audience tokens are now verified per Google's reference
  implementation: they are JWTs self-signed by
`chat@system.gserviceaccount.com`, so the adapter checks them against
that
  service account's X.509 certificates with issuer
`chat@system.gserviceaccount.com` (previously it used `verifyIdToken`,
which
only accepts Google OIDC issuers and certs and therefore rejected every
real
project-number token). When both verifiers are configured, either token
type
  is accepted.

  The adapter still infers an endpoint URL from incoming requests for
button-click action routing only — that inferred value is never used as
a
JWT verification audience, and inference now only happens after a
request
has passed verification (or verification was explicitly disabled),
because
  `request.url` derives from the attacker-controllable `Host` header in
  serverless runtimes.

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/github@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/linear@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/messenger@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/shared@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
## @chat-adapter/twilio@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/web@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/whatsapp@4.35.0

### Patch Changes

- 09b72e9: fix whatsapp card media duplication

- Prevent card titles and body content from appearing twice when sending
cards with files on WhatsApp.
- Avoid adding the full card fallback text as an image caption when an
interactive message follows.
- Keep interactive WhatsApp messages responsible for rendering card
titles, bodies, and actions.

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/x@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
  - @chat-adapter/shared@4.35.0
## @chat-adapter/state-ioredis@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
## @chat-adapter/state-memory@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
## @chat-adapter/state-pg@4.35.0

### Patch Changes

- d88789c: Fix `setIfNotExists()` so it can claim a cache key whose
existing row has expired. Previously the query used `ON CONFLICT DO
NOTHING`, so an expired row in `chat_state_cache` still blocked
acquisition until opportunistic cleanup deleted it — diverging from the
memory and Redis adapters, which treat expired entries as absent. Keys
stored without a TTL remain permanent and are never overwritten.
- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
## @chat-adapter/state-redis@4.35.0

### Patch Changes

- Updated dependencies [80def3a]
- Updated dependencies [4cb7e5d]
- Updated dependencies [46681f5]
- Updated dependencies [93a58af]
- Updated dependencies [25f3099]
  - chat@4.35.0
## @chat-adapter/tests@4.35.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-24 22:21:13 +01:00
Max 54eea71501 feat(telegram): add user allowlist (#742)
## Summary

Add an opt-in `allowedUserIds` Telegram adapter option, with
`TELEGRAM_ALLOWED_USER_IDS` as a comma-separated environment fallback.
Updates from other or unidentified users are ignored before dispatch.

This follows the adapter-level targeting pattern from [the Discord
channel response allowlist](https://github.com/vercel/chat/pull/715),
while enforcing an ingress allowlist instead of expanding mention
routing.

## Test plan

- `pnpm --filter @chat-adapter/telegram test`
- `pnpm --filter @chat-adapter/telegram typecheck`
- `pnpm check`
- `pnpm konsistent`
- `TURBO_CONCURRENCY=2 pnpm validate`

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
2026-07-24 21:06:44 +01:00
Aamir Jawaid 160140e32b feat(teams): add targeted ephemeral messages (#737)
## Summary

Microsoft Teams supports targeted messages that are visible only to a
selected conversation member, but the Teams adapter did not expose that
native behavior through the SDK's ephemeral-message API. This PR wires
`postEphemeral` for Teams to send native targeted messages while
preserving normal `postMessage` behavior by default.

The adapter now creates explicit targeted outbound activities with
`MessageActivity.withRecipient(recipient, true)` for text and
adaptive-card messages, returns `usedFallback: false`, and keeps the
feature gated behind `thread.postEphemeral()` /
`channel.postEphemeral()`. It also bumps the Teams SDK packages to
`^2.0.13`, adds targeted coverage, updates public docs/matrices, and
includes a changeset.

Live verification found that Teams targeted messages require the app to
be installed in the shared conversation. Group chats and channels both
worked after using the Teams install picker with `Open -> select
placement -> Go`; personal bot chat targeted sends returned a Teams
`BadArgument` response.

## Test plan

Previously validated with:

- `corepack pnpm --filter @chat-adapter/teams exec vitest run
src/index.test.ts --coverage.enabled=false`
- `corepack pnpm --filter @chat-adapter/teams exec tsc --noEmit`
- `corepack pnpm --filter example-nextjs-chat exec tsc --noEmit`
- `corepack pnpm --filter chat exec vitest run src/emoji.test.ts
--coverage.enabled=false`
- `corepack pnpm --filter @chat-adapter/teams exec tsup`
- Targeted `ultracite check` on changed files

Live verified `TeamsAdapter.postEphemeral(...)` in:

- Group chat `Demo Test 2`: Teams UI showed `Only you can see this
message`.
- Channel `General / Teams SDK`: Teams returned message ID
`1784749118197`, and the UI showed `Only you can see this message`.

<img width="884" height="299" alt="Screenshot 2026-07-22 at 12 41 41 PM"
src="https://github.com/user-attachments/assets/cd350ac8-c158-4779-8028-3450eb8670f2"
/>
<img width="1098" height="559" alt="Screenshot 2026-07-22 at 12 41
33 PM"
src="https://github.com/user-attachments/assets/bf6ea12b-ab11-46ee-a3a8-ff5e9583066d"
/>


## Checklist

- [ ] All commits are signed and verified - unsigned commit created
after local GPG/SSH signing was unavailable and user approved continuing
- [ ] `pnpm validate` passes - full validate not run; targeted
validation listed above
- [x] Changeset added (or N/A - see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)

---------

Co-authored-by: dancer <josh@afterima.ge>
Copilot-Session: 601a7414-48f0-4e6d-ba03-28fa4d2d5c0a
2026-07-23 19:53:13 +01:00
Aradhya C P 09b72e9dd3 fix(whatsapp): stop duplicating card title when posting Card with files (#736)
## Description

Fixes a WhatsApp adapter bug where posting a **Card together with
files** caused the card title (and other card text) to appear twice:
once in the media caption (from `cardToFallbackText`) and again in the
interactive message header/body (from `cardToWhatsApp`).

`postMessageWithMedia` now checks whether the card will be sent as an
interactive message first. If so, it skips using the full card fallback
as the media caption and lets the interactive message own the title,
body, and buttons. Text-fallback cards + files keep the previous caption
behavior (single message, no duplicate text).

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Refactoring (no functional changes)

## Related Issues

Fixes #735
Closes #735
Related to #735

<!-- Replace # with the issue number after opening the bug report -->

## Changes Made

- In `postMessageWithMedia`, compute `cardToWhatsApp(card)` first and
reuse that result.
- When the card is **interactive**, do not set media caption from
`cardToFallbackText` (empty caption text) so title/body are not
duplicated on the image.
- When the card is **text fallback**, keep captioning media with
`cardToFallbackText` and avoid sending a second text message (existing
behavior).
- Expand unit coverage in `index.test.ts` for interactive + files (title
once, no caption duplication for text/fields, multi-file, audio, HTTPS
attachment) and text-fallback caption behavior.

## Testing

- [x] All existing tests pass
- [x] Added new tests for the changes
- [x] Manually tested the changes

### Test Coverage

- Built the package, then used `pnpm link` to link the built `dist` into
a separate test project.
- Exercised `thread.post({ card, files })` against the reported bug
scenario and confirmed the title no longer appears twice (caption empty
for interactive cards; title only on the interactive message).
- Added / updated unit tests in
`packages/adapter-whatsapp/src/index.test.ts`; all related tests pass
locally (`pnpm --filter @chat-adapter/whatsapp test`).

## Screenshots/Demos

<!-- Paste before/after WhatsApp screenshots here -->

**Before (title duplicated on caption + interactive header):**

<img width="433" height="428" alt="image"
src="https://github.com/user-attachments/assets/37c776fd-b4f6-48f3-a365-6e2073316576"
/>



**After (title only on interactive message; media uncaptioned):**

<img width="428" height="390" alt="image"
src="https://github.com/user-attachments/assets/fb1f43d9-623a-4e22-83ea-dd96cd6d3877"
/>

<img width="408" height="405" alt="image"
src="https://github.com/user-attachments/assets/3959eb04-e722-43f4-8b85-91b724faebcb"
/>


## Checklist

- [x] My code follows the project's code style
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings or errors
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have created a changeset (`pnpm changeset`)
- [x] All commits are signed and verified
- [ ] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [ ] Documentation updated (or N/A)

## Changeset

- [x] I have created a changeset for these changes

<!-- Reminder: behavioural package changes need `pnpm changeset` for
`@chat-adapter/whatsapp` -->

## Additional Notes

- No change to `@chat-adapter/shared`’s `cardToFallbackText` — it
remains correct as a full text fallback. The bug was reusing that full
fallback as a caption while also sending a full interactive card.
- Card-only posts (no files) are unchanged.

---------
2026-07-23 19:18:25 +01:00
Akazawa Daisuke 92530dd352 fix(slack): return replyable thread ID from channel posts (#720)
## summary

fixes #719

updates Slack channel posts to return a replyable thread ID rooted at
the newly posted top-level message

`postChannelMessage` still omits `thread_ts` for the initial channel
post, then uses Slack's returned message `ts` to construct
`slack:<channel>:<ts>` for the `SentMessage`

this allows `chat.thread(sent.threadId).post(...)` to reply to a message
returned by `channel.post(...)` instead of creating another top-level
channel message

file-only uploads without a Slack message `ts` keep their existing
channel-scoped thread ID

## test plan

- added a Slack adapter regression assertion that `postChannelMessage`
returns `slack:C123:<message ts>`
2026-07-23 19:05:34 +01:00
Utopia 5eb8b846a7 feat(teams): support outbound reactions (#734)
Outbound Teams reactions were originally implemented as part of #302,
then removed because the Teams feature was not fully rolled out. In [the
follow-up
discussion](https://github.com/vercel/chat/pull/302#issuecomment-4147056867),
the Teams SDK maintainer said they were happy to add the support back
once the rollout was ready. Microsoft now documents agent reaction
support without a preview caveat.

This PR restores that support against the current Teams SDK API:

- implement `addReaction` and `removeReaction` with
`conversations.addReaction` / `conversations.deleteReaction`
- pass native Teams reaction IDs through unchanged and map common
normalized Chat SDK emoji names to their Teams IDs
- upgrade the aligned `@microsoft/teams.*` dependencies to 2.0.14
- update the Teams feature matrices and add a minor changeset

The implementation stays within the existing adapter methods and does
not add another abstraction or affect streaming behavior.

---------

Signed-off-by: Utopia <154325211+Utopi-a@users.noreply.github.com>
2026-07-23 10:52:00 +10:00
Cole 25f30998ce fix(chat): keep attachment/link-only messages in toAiMessages (#713)
- `toAiMessages` previously filtered out any message with empty or
whitespace-only text (`sorted.filter((msg) => msg.text.trim())`). This
discarded messages that carry meaningful content without text — e.g. an
image uploaded with no caption, a file-only upload, or a link-only
message.
- Now messages are kept as long as they have usable content (text,
image/file attachments, or links). Only messages with *none* of those
are skipped.
- When an attachment-only message is included, no empty `text` part is
prepended (an empty text part would be rejected by the AI SDK).
Link-only messages render a standalone `Links:\n...` block.

## Changes

- `packages/chat/src/ai/messages.ts` — drop the text-only pre-filter;
build text conditionally; skip only truly empty messages.
- `packages/chat/src/ai/messages.test.ts` — add tests for image-only,
link-only, interleaved, whitespace-with-attachment, and fully-empty
cases.
- `apps/docs/content/docs/ai/to-ai-messages.mdx` — update the documented
filtering behavior.
- Changeset added (`chat`: patch).

---------

Co-authored-by: Cole Corrente <cole.corrente@snowflake.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-22 14:40:41 +10:00
CamdenA21 e06b4b606b feat(teams): forward a custom token factory to the Teams SDK (#732)
`TeamsAdapterConfig` never forwards a `token` field through to the
underlying `@microsoft/teams.apps` `AppOptions.token`, even though the
Teams SDK already supports it as a genuine "bring your own credentials"
escape hatch (`TokenCredentials['token']`).

The only non-secret auth path currently exposed is `federated`, which
maps to `managedIdentityClientId` and only resolves via Azure-native
managed-identity sources (IMDS, AppService, CloudShell, MachineLearning,
ServiceFabric). That's unreachable from serverless/edge runtimes (e.g.
Vercel) that can't hit Azure IMDS but still need to mint access tokens
through an external mechanism (e.g. a workload-identity federation
bridge exchanging a platform-native OIDC token for an Azure AD token).

We've been carrying a local patch on `@chat-adapter/teams` doing exactly
this forwarding to unblock a production Teams bot running on Vercel with
a user-assigned managed identity. Opening this as a proper PR instead of
staying on the patch indefinitely.

## Changes

- `TeamsAdapterConfig.token?: (scope: string | string[], tenantId?:
string) => string | Promise<string>` — matches
`TokenCredentials['token']`'s real signature.
- `toAppOptions` forwards `config.token` straight through.
- `clientSecret` resolution now also short-circuits when `token` is
provided (alongside the existing `federated` check) —
`TokenManager.initializeCredentials` checks `clientId && clientSecret`
before `clientId && token`, so a stray
`appPassword`/`TEAMS_APP_PASSWORD` would otherwise silently win over an
explicitly configured token factory.
- Unit tests in `config.test.ts` and a `createTeamsAdapter` factory test
in `index.test.ts`.
- Docs: added the `token` config option and a third "Authentication
methods" example in `apps/docs/content/adapters/official/teams.mdx`.
- Changeset (`@chat-adapter/teams`: minor).

---------

Signed-off-by: CamdenA21 <camden@sandstone.ai>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-22 13:18:59 +10:00
dependabot[bot] b4a93bdbc5 build(deps-dev): bump @hono/node-server from 2.0.2 to 2.0.10 (#733)
Bumps [@hono/node-server](https://github.com/honojs/node-server) from
2.0.2 to 2.0.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/honojs/node-server/releases">@​hono/node-server's
releases</a>.</em></p>
<blockquote>
<h2>v2.0.10</h2>
<h2>Security fixes</h2>
<p>This release includes a fix for the following security issue:</p>
<h3>Unauthenticated memory-leak DoS via aborted WebSocket handshake</h3>
<p>Affects: <code>upgradeWebSocket</code>. A WebSocket upgrade request
with a missing or malformed <code>Sec-WebSocket-Key</code> header leaked
the request's <code>IncomingMessage</code> and left a promise pending,
even though no connection was established. Since the route is reachable
pre-handshake without authentication, an attacker could flood it to
gradually exhaust memory. <a
href="https://github.com/honojs/node-server/security/advisories/GHSA-9mqv-5hh9-4cgg">GHSA-9mqv-5hh9-4cgg</a></p>
<hr />
<p>Users of <code>upgradeWebSocket</code> are encouraged to upgrade to
this version.</p>
<h2>v2.0.9</h2>
<h2>What's Changed</h2>
<ul>
<li>fix(websocket): polyfill missing ErrorEvent global by <a
href="https://github.com/otnc"><code>@​otnc</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/371">honojs/node-server#371</a></li>
<li>fix(serve-static): correct Range header parsing edge cases by <a
href="https://github.com/otnc"><code>@​otnc</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/372">honojs/node-server#372</a></li>
<li>fix: recover complete request bodies after client disconnect by <a
href="https://github.com/usualoma"><code>@​usualoma</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/375">honojs/node-server#375</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/otnc"><code>@​otnc</code></a> made their
first contribution in <a
href="https://redirect.github.com/honojs/node-server/pull/371">honojs/node-server#371</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/honojs/node-server/compare/v2.0.8...v2.0.9">https://github.com/honojs/node-server/compare/v2.0.8...v2.0.9</a></p>
<h2>v2.0.8</h2>
<h2>What's Changed</h2>
<ul>
<li>ci(release): add <code>--no-git-checks</code> option for <code>pnpm
stage publish</code> by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/369">honojs/node-server#369</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/honojs/node-server/compare/v2.0.7...v2.0.8">https://github.com/honojs/node-server/compare/v2.0.7...v2.0.8</a></p>
<h2>v2.0.7</h2>
<h2>What's Changed</h2>
<ul>
<li>chore: migrate to pnpm by <a
href="https://github.com/BlankParticle"><code>@​BlankParticle</code></a>
in <a
href="https://redirect.github.com/honojs/node-server/pull/367">honojs/node-server#367</a></li>
<li>fix(serve-static): serve precompressed files for
application/octet-stream by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/366">honojs/node-server#366</a></li>
<li>chore: bump <code>supertest</code> by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/368">honojs/node-server#368</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/honojs/node-server/compare/v2.0.6...v2.0.7">https://github.com/honojs/node-server/compare/v2.0.6...v2.0.7</a></p>
<h2>v2.0.6</h2>
<h2>What's Changed</h2>
<ul>
<li>ci: publish to npm from CI with OIDC trusted publishing and bump
<code>np</code> by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/361">honojs/node-server#361</a></li>
<li>ci: use npm Staged publishing by <a
href="https://github.com/yusukebe"><code>@​yusukebe</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/364">honojs/node-server#364</a></li>
<li>fix: preserve status and statusText when cloning a Response with
liveheaders by <a
href="https://github.com/usualoma"><code>@​usualoma</code></a> in <a
href="https://redirect.github.com/honojs/node-server/pull/363">honojs/node-server#363</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/honojs/node-server/compare/v2.0.5...v2.0.6">https://github.com/honojs/node-server/compare/v2.0.5...v2.0.6</a></p>
<h2>v2.0.5</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/honojs/node-server/commit/7c1457ed5536c02fdd2f001129fae67bcbca54a1"><code>7c1457e</code></a>
2.0.10</li>
<li><a
href="https://github.com/honojs/node-server/commit/3a21938c418340e980cb7ffa88e78369f78392d1"><code>3a21938</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/honojs/node-server/commit/98420217e53a17a238ef1aa1a6bef0b2b70136c5"><code>9842021</code></a>
2.0.9</li>
<li><a
href="https://github.com/honojs/node-server/commit/51f3bf56f56d9691ec0f7e1562a96f0b485a7dd9"><code>51f3bf5</code></a>
fix: recover complete request bodies after client disconnect (<a
href="https://redirect.github.com/honojs/node-server/issues/375">#375</a>)</li>
<li><a
href="https://github.com/honojs/node-server/commit/fdb87badbe313cfbfe6bb2355e9893dc0698d2bd"><code>fdb87ba</code></a>
fix(serve-static): correct Range header parsing edge cases (<a
href="https://redirect.github.com/honojs/node-server/issues/372">#372</a>)</li>
<li><a
href="https://github.com/honojs/node-server/commit/912e3fd80c4311756f724bd566de1433c8d772d9"><code>912e3fd</code></a>
fix(websocket): polyfill missing ErrorEvent global (<a
href="https://redirect.github.com/honojs/node-server/issues/371">#371</a>)</li>
<li><a
href="https://github.com/honojs/node-server/commit/114c15efb38dabaf81af774ddb764409e3d156d8"><code>114c15e</code></a>
2.0.8</li>
<li><a
href="https://github.com/honojs/node-server/commit/5db2d5df662cd69ff5c4cc23b8ecb3a6f63e4e38"><code>5db2d5d</code></a>
ci(release): add <code>--no-git-checks</code> option for <code>pnpm
stage publish</code> (<a
href="https://redirect.github.com/honojs/node-server/issues/369">#369</a>)</li>
<li><a
href="https://github.com/honojs/node-server/commit/a528a77ed2c28dc12775c849abc6b6df6d4cb44c"><code>a528a77</code></a>
2.0.7</li>
<li><a
href="https://github.com/honojs/node-server/commit/b2d610c1e37a96639fbb2eae662e858800aa8906"><code>b2d610c</code></a>
chore: bump <code>supertest</code> (<a
href="https://redirect.github.com/honojs/node-server/issues/368">#368</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/honojs/node-server/compare/v2.0.2...v2.0.10">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for <code>@​hono/node-server</code> since your current
version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@hono/node-server&package-manager=npm_and_yarn&previous-version=2.0.2&new-version=2.0.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/vercel/chat/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-22 13:15:31 +10:00
Ben Sabic 4cb7e5d58e feat(chat): durable human-in-the-loop approvals via chat/workflow (#728)
Adds a `chat/workflow` subpath export with `requestApproval()`. This is
the DX from #284, rebuilt on Workflow SDK so the approval survives
deploys, restarts, and arbitrarily long waits. No in-memory promises, no
approvals registry, no restart-recovery machinery: the workflow suspends
on a webhook and resumes when a button is clicked.

`requestApproval()` posts a card with Approve/Deny buttons whose
`callbackUrl` targets a `createWebhook()` URL, suspends the workflow
until a decision (or optional durable-sleep timeout), validates
approvers, finalizes the card in place with the outcome (removing the
buttons, leaving an audit trail), and returns the decision.

```typescript
import { requestApproval } from "chat/workflow";
import type { Thread } from "chat";
export async function deployApproval(opts: { thread: Thread; version: string }) {
  "use workflow";
  const { approved, user, timedOut } = await requestApproval(opts.thread, {
    title: `Deploy ${opts.version}?`,
    fields: { Version: opts.version },
    timeout: "24h",
    approvers: ["U_ALICE", "U_BOB"],
  });
  if (approved) {
    await deploy(opts.version);
  }
}
```

Starting it from a handler is one line. `Thread` instances serialize
across the workflow boundary automatically via the existing
`@workflow/serde` hooks on `ThreadImpl` (requires
`chat.registerSingleton()`):

```typescript
import { start } from "workflow/api";
bot.onNewMention(async (thread, message) => {
  await start(deployApproval, [{ thread, version: parseVersion(message.text) }]);
});
```

**Details**

- `workflow` is a new **optional** peer dependency (same pattern as
`ai`); the subpath is the only code that imports it
- Unauthorized clicks (when `approvers` is set) and unrecognizable
payloads post a notice / are ignored, and the workflow keeps waiting
- On timeout the card is finalized as timed out and the result has
`timedOut: true`
- Card builders (`buildApprovalCard`, `buildResolvedCard`) are exported
for custom flows
- Verified the published `dist` preserves the `"use step"` directives
and down-levels `using` correctly, so the app-side Workflow SDK compiler
handles the library code
- Docs page under Interactivity; changeset (`chat` minor); 8 unit tests
mocking the `workflow` primitives

**Deliberate deviation from #284:** no `thread.requestApproval()`
method. The function must suspend at workflow level, so hanging it off
`ThreadImpl` would make `workflow` a hard dependency of core (or require
prototype patching). The standalone `requestApproval(thread, options)`
keeps the dependency optional.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-22 10:18:38 +10:00
Ben Sabic 907450d73f fix(slack): Enterprise Grid support (#724)
## Slack Enterprise Grid Support

Fixes a set of Enterprise Grid gaps in the Slack adapter, found by
auditing the adapter against Slack's current Enterprise documentation
([Developing for Enterprise
orgs](https://docs.slack.dev/enterprise/developing-for-enterprise-orgs/),
[Events API](https://docs.slack.dev/apis/events-api/),
[oauth.v2.access](https://docs.slack.dev/reference/methods/oauth.v2.access/)).

The headline bug: org-wide installs completing OAuth through
`handleOAuthCallback` were stored under a key that webhook token
resolution never looks up, so every subsequent event failed with "no
installation found."

### Org-wide OAuth installs

`oauth.v2.access` returns `team: null` and a populated `enterprise`
object for org-wide installs (`is_enterprise_install: true`). The
callback previously keyed every installation by `result.team.id`. It now
keys org-wide installs by `enterprise.id` — the same key incoming
webhooks resolve tokens by — and records `enterpriseId` /
`isEnterpriseInstall` on `SlackInstallation`. The returned `teamId` is
always the storage key, so it keeps round-tripping with
`getInstallation` / `deleteInstallation` for both install types.

### Socket mode token resolution

Socket-mode events (live and forwarded from a serverless listener)
bypassed the multi-workspace token-resolution block in `handleWebhook`
entirely and dropped `enterprise_id` / `is_enterprise_install` when
rebuilding the payload. The resolution logic is now factored into
helpers (`resolveEventRequestContext`, `runSlashCommand`,
`extractInstallationFromInteractivePayload`) shared by both paths, so
events, slash commands, and interactive payloads resolve
per-installation tokens identically over HTTP and socket. Socket JSON
delivers `is_enterprise_install` as a boolean where form-encoded
webhooks deliver `"true"`; both shapes are handled.

### Event routing via `authorizations[0]`

Slack documents the envelope's `authorizations[0]` — not the top-level
fields — as the authoritative installation identity for an event. The
top-level `team_id` / `enterprise_id` can name a different workspace for
Slack Connect shared-channel events (slackapi/bolt-js#935), and org-wide
envelopes may omit the top-level flags. Token resolution now prefers
`authorizations[0]` with top-level fallback, matching Bolt's behavior.

### Installation-scoped user caches

The user profile cache (`slack:user:*`) and display-name mention reverse
index (`slack:user-by-name:*`) were global across installations. In
multi-workspace deployments, one tenant's cached profile bled into
another, and mention resolution could pick a same-named user from a
different workspace. Both are now prefixed with the current installation
ID (enterprise ID for org-wide installs — correct on Grid, where users
are org-global). Single-workspace keys are unchanged; scoped entries
repopulate on first lookup.

### `team_id` on org-token API calls

Org-wide tokens span every workspace in the org, so workspace-scoped Web
API methods (`conversations.list`, `usergroups.list`, …) require an
explicit `team_id`. `withToken` now injects the event's `team_id` on
calls made under an org-wide install when the caller didn't set one —
Slack documents always passing it as safe ("accepted, but ignored"
elsewhere). When an event arrives from a shared channel hosted on an
"away" workspace, its `context_team_id` is echoed back as
`client_context_team_id`, scoped to calls targeting the originating
channel.

### Event retry deduplication

Retried deliveries (`x-slack-retry-num` header, socket `retry_num`) are
dropped when the original delivery was already dispatched, using an
`event_id` marker in the state adapter (24 h TTL, covering Slack's
opt-in Delayed Events redeliveries). First deliveries pay no state read,
and events whose first delivery never arrived are still recovered via
the retry — preserving the existing missed-event recovery semantics.

### W-prefixed user IDs

Grid users can have `W…` IDs anywhere `U…` appears. The outgoing
bare-mention scanner only recognized `@U…` as a raw user ID; it now
accepts both. This was the only `^U` assumption in the package —
incoming mention parsing was already prefix-agnostic.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-21 09:10:52 +10:00
Ben Sabic bb7cd1241d feat(slack): expose author email on incoming messages (#716)
Slack counterpart to #711: populates `message.author.email` on
normalized incoming Slack messages.

Unlike Teams, no new lookup was needed — `parseSlackMessage` already
resolves the sender via the state-cached `users.info` call
(`lookupUser`), and the cached profile already carried `email`. This
change threads that value onto the author, so there is no additional API
call per message. The email is only present when the app has the
`users:read.email` scope; otherwise (or when the lookup is skipped, e.g.
webhook posts with a `username`) the field stays `undefined`.

Also documents the optional scope in the adapter README and the docs
site manifest section. The core `Author.email` field and serialization
already landed in #711, so this is a Slack-only changeset.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-21 07:44:08 +10:00
Max 26c052258c feat(discord): add channel response allowlist (#715)
## Summary

Adds an opt-in `respondToChannelIds` Discord adapter option. Non-bot
messages in configured parent channels and their child threads are
routed through mention handlers without requiring an @mention; top-level
messages keep the adapter's existing automatic thread creation, and
forwarded Gateway packets preserve the parent channel for thread
replies.

I understand this might be something you want to keep out but I find it
very useful for my own "Hermes-like" agent :)

## Test plan

- `pnpm --filter @chat-adapter/discord test`
- `pnpm --filter @chat-adapter/discord typecheck`
- `pnpm check`
- `pnpm konsistent`
- `pnpm typecheck`
- `pnpm validate`

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)

---------

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-18 22:29:56 +10:00
Dean Mauro b605cf63a2 fix(discord): Suppress link previews in markdown (#726)
## Summary

Fixes #692 

When bracketed `<link>` or `[text](<link>)` notations are used in
Discord markdown, embedded link previews should be suppressed.

We have to identify this notation in the markdown parser and decorate
the resulting AST so the `[text](<link>)` notation can be handled as
well. Otherwise, this information gets lost along the way.

## Test plan

```
await event.channel.post({ markdown: '<https://google.com>'});
```

<img width="172" height="27" alt="image"
src="https://github.com/user-attachments/assets/386ea82d-d00c-4ebd-a29b-b2cd495e973e"
/>


```
await event.channel.post({ markdown: '[Google](<https://google.com>)'});
```

<img width="199" height="29" alt="image"
src="https://github.com/user-attachments/assets/0faea900-1742-482e-b477-0b5e5ac87316"
/>


## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)
2026-07-18 22:10:35 +10:00
Ben Sabic 80def3ab17 feat: add author.isSystem to distinguish platform-generated messages (#707)
Closes #653

Chat SDK's normalized author only distinguished the current bot from
other bots (`isBot`/`isMe`), so Slack system notifications authored by
the reserved `USLACK` user — which carry no `bot_id` and no system
subtype — were dispatched to handlers as if human-authored. Consumers
had to hard-code `message.author.userId === "USLACK"`, leaking
Slack-specific identifiers into adapter-independent code.

This adds an optional `isSystem?: boolean` to the normalized `Author`
type, documented so that an absent value means `false`. Keeping it
optional avoids breaking existing custom adapters and serialized
messages, as proposed in the issue. The Slack adapter now sets it in
both parse paths (`parseSlackMessage` and the sync `parseMessage` path)
via a `SLACK_SYSTEM_USER_ID` constant, so applications can write:

```ts
bot.onNewMention(async (thread, message) => {
  if (message.author.isSystem) {
    return;
  }
  await generateAssistantResponse(thread, message);
});
```

Other adapters can adopt the same field when their platforms expose
equivalent system-generated messages.

Also included:

- Regression tests covering the issue's exact case (`USLACK` DM, no
`bot_id`, no subtype) across all three layers: async parse, sync
`parseMessage`, and end-to-end `handleWebhook` dispatch — plus the
negative case for human authors.
- A `USLACK` webhook fixture in `sample-messages.md`.
- `isSystem` documented in the Author type table on the Message API docs
page.
- Changesets for `chat` and `@chat-adapter/slack`.

---------

Co-authored-by: mdnanocom <arnaud@massive-dynamic.ai>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-16 00:46:05 +10:00
Jörmungandrk 0701679e40 fix: Enhance mention regex and sleep method with abort support (#706)
Refactor mention regex handling and sleep method to support abort
signal. a new `RegExp` is compiled on every call to `isBotMentioned`,
which is invoked for every incoming message. Since `username` is stable
after initialization, this regex can be compiled once (e.g. cached in a
private field and updated when `_userName` changes) and reused across
calls.


## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [ ] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [ ] Documentation updated (or N/A)

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-15 14:58:53 +10:00
mdnanocom 270b1c2592 fix(gchat): accept endpointUrl as a direct-webhook JWT audience (#518)
## Summary

When a Google Chat app's connection setting **Authentication audience**
is set to **HTTP endpoint URL** (Google's recommended option for
HTTP-hosted apps that aren't behind Cloud Run IAM, see [Verify requests
from Google Chat][1]),
the bearer token Google sends is an OIDC ID token whose `aud` is the
endpoint URL — not the GCP project number.
The adapter previously only verified against `googleChatProjectNumber`,
so URL-audience tokens always failed with
`401 Unauthorized` and direct webhooks silently broke for any app
configured this way.

This change makes the adapter verify direct-webhook JWTs against
`googleChatProjectNumber` and/or `endpointUrl`, accepting either when
both are configured (handy for multi-env setups that mix the two modes).
The constructor's fail-closed check accepts an explicit `endpointUrl` as
a valid direct-webhook verifier alongside `googleChatProjectNumber`,
`pubsubAudience`, and `disableSignatureVerification`.

[1]:
https://developers.google.com/workspace/chat/verify-requests-from-chat

## Behavior

| Config | Direct-webhook `aud` accepted |
| ----------------------------------------------------------- |
--------------------------------- |
| `googleChatProjectNumber` only (current behavior) | project number |
| `endpointUrl` only (**new**) | endpoint URL |
| Both `googleChatProjectNumber` and `endpointUrl` (**new**) | either |
| Neither, no `pubsubAudience`, no `disableSignatureVerification` |
constructor throws (unchanged) |

### Security note

Auto-detected endpoint URLs (the value `handleWebhook` falls back to
from the incoming `request.url` when `endpointUrl` is not configured)
are intentionally **not** promoted to verifier status. Treating an
auto-detected URL as a valid audience would let any caller bypass
verification by hitting the bot at a URL of their choice.
A new `endpointUrlIsAudience` flag captures whether the caller
explicitly configured `endpointUrl`, and only that case enables
URL-based verification. A regression test guards this.

## Implementation

- `verifyBearerToken` accepts `string | string[]`
(`OAuth2Client.verifyIdToken`
  already supports both).
- `handleWebhook` builds `directAudiences = [projectNumber,
explicitEndpointUrl].filter(Boolean)` and passes a single string when
only one verifier is configured (to preserve the prior call shape) or an
array when both are.
- Docs updated in `apps/docs/content/adapters/official/google-chat.mdx`:
describe both authentication-audience modes and document `endpointUrl`
as an accepted verifier.
- Changeset: `@chat-adapter/gchat: patch`.

## Test plan

- [x] `pnpm --filter @chat-adapter/gchat test` — 250/250 pass (5 new
tests).
- [x] `pnpm typecheck` — 33/33 tasks pass.
- [x] `pnpm check` (Ultracite/Biome) clean.
- [x] `pnpm konsistent` clean.
- [x] Validated end-to-end against a real Google Chat app configured
with
"HTTP endpoint URL" as authentication audience: webhooks that previously
      401'd now verify and process correctly.

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-15 14:41:15 +10:00
zhygis d88789c982 fix(state-pg): setIfNotExists TTL expiry (#636)
## Summary

Fixes the Postgres state adapter so `setIfNotExists()` can claim a cache
key whose existing row has expired.

Previously the method used `ON CONFLICT DO NOTHING`, so an expired row
in `chat_state_cache` still blocked acquisition until a separate cleanup
deleted it. That diverged from the memory adapter behavior and from the
expected lease semantics for cache-backed coordination.

The new query keeps active rows protected, but replaces rows whose
`expires_at` is in the past.

## Validation

- `pnpm --filter @chat-adapter/state-pg test`
- `pnpm --filter @chat-adapter/state-pg typecheck`
- `pnpm exec ultracite check packages/state-pg/src/index.ts
packages/state-pg/src/index.test.ts`

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-15 14:39:32 +10:00
Max 46681f50cb fix(teams): hydrate incoming author email (#711)
## Summary

Adds optional email to normalized message authors and preserves it
through message serialization.

For incoming Teams messages, resolves the sender with the activity's
Entra object ID before dispatch, falling back to the cached ID when the
activity omits it. The lookup reuses the existing `mail ??
userPrincipalName` mapping from #708, and missing permissions or Graph
failures leave email undefined without blocking message delivery.

This deliberately revisits the author-profile boundary discussed in
#239: the core field is optional, and Teams populates it only when
Microsoft Graph can resolve the sender.

## Test plan

- [x] `pnpm --filter chat exec vitest run src/message.test.ts`
- [x] `pnpm --filter @chat-adapter/teams exec vitest run
src/index.test.ts`
- [x] Chat and Teams package typechecks and builds
- [x] `TURBO_CONCURRENCY=2 pnpm validate`

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)

---------

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-15 14:37:56 +10:00
Max 93a58af563 fix(teams): preserve native streaming with placeholders (#709)
## Summary

Preserves Teams native DM streaming when
`fallbackStreamingPlaceholderText` is explicitly configured. Direct
messages show the text through the Teams SDK native informative status
before streaming the answer, group chats use the core post-and-edit
fallback, `null` disables progress, and omitted configuration keeps the
existing native-DM/buffered-group behavior.

## Test plan

- [x] `pnpm --filter @chat-adapter/teams exec vitest run
src/index.test.ts`
- [x] `pnpm --filter chat exec vitest run src/chat.test.ts
src/thread.test.ts`
- [x] Teams and Chat package typechecks and builds
- [x] `TURBO_CONCURRENCY=2 pnpm validate`

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
2026-07-15 01:15:15 +10:00
Max 3895ab3fa9 fix(teams): fall back to user principal name for email (#708)
## Summary

Use Microsoft Graph's `userPrincipalName` as the Teams user's email when
`mail` is missing, while preserving `mail` precedence when both fields
are present.

## Test plan

- [x] `pnpm --filter @chat-adapter/teams test`
- [x] `pnpm --filter @chat-adapter/teams typecheck`
- [x] `pnpm --filter @chat-adapter/teams build`
- [x] `TURBO_CONCURRENCY=2 pnpm validate`

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (N/A — no public API change)

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
2026-07-15 01:10:10 +10:00
github-actions[bot] f84b5911e7 chore(release): version packages (#695)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @chat-adapter/discord@4.34.0

### Minor Changes

- 0fdb902: Add opt-in Discord Components v2 rendering for Chat SDK cards
via `contentFormat: DiscordContentFormat.ComponentsV2`.
- 5341f90: Ignore `@everyone`/`@here` pings by default in gateway mode.
Previously the legacy gateway listener treated global pings as bot
mentions, so the bot responded to announcements. A new
`respondToGlobalMentions` config option (default `false`) restores the
old behavior when enabled, and also lets forwarded gateway messages opt
in via the `mention_everyone` field.
- 6c2a391: Add support for renaming native Discord thread channels.

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/slack@4.34.0

### Minor Changes

- 1721fa0: Add support for Slack's Agent messaging experience
(`agent_view`).

- New core event `onAppContextChanged` with a normalized
`AppContextEntity[]` describing the user's active view (channel / canvas
/ list / message / unknown).
- `AppHomeOpenedEvent` now carries the folded active-view context as
`entities` and the opened `tab` (Slack: `"home"` / `"messages"`), so
handlers can tell a Home-tab open from the DM-open signal under
`agent_view`.
- Slack adapter: new `agentView` config flag (under `agent_view`,
`app_home_opened` is the DM-open signal regardless of tab and folded
context is surfaced), routing for the `app_context_changed` event, and a
`getAppContext(message)` helper to read the folded context on DM
messages.
- `setSuggestedPrompts` now accepts an optional thread reference
(agent_view lets prompts sit at the top of the agent conversation).
- Under `agentView`, DM (Messages-tab) messages are threaded per new
Slack's model — each user message is a thread root (`thread_ts ?? ts`).
Conversation-scoped threads returned by `openDM()` keep working: when
that thread is subscribed, incoming top-level DM messages route to it.
- `createSlackAdapter` env auth fallback (`SLACK_BOT_TOKEN` /
`SLACK_CLIENT_ID` / `SLACK_CLIENT_SECRET`) is now disabled only when an
auth or verification field (`botToken`, `clientId`, `clientSecret`,
`installationProvider`, `signingSecret`, `webhookVerifier`) is passed
explicitly, instead of by any config object — so `createSlackAdapter({
agentView: true })` still picks up env credentials, while
explicit-secret configs stay immune to ambient env vars.
- Bumped `@slack/web-api` to `^7.18.0` (adds the optional `thread_ts`
typing for `setSuggestedPrompts`).

- 4717a38: Add chart support and richer table rendering, with native
Slack data table and data visualization blocks.

- New core `ChartElement` and `Chart()` builder (JSX supported) with
pie, bar, area, and line charts, mirroring Slack's data visualization
model: pie charts take `segments`, series charts take named `series`
plotted against shared `categories` with optional `xLabel`/`yLabel`.
- `TableElement` / `Table()` gain optional `caption` (accessible table
description) and `pageSize` (rows per page) fields.
- Charts degrade gracefully on platforms without native chart support:
the underlying data renders as a text table via the shared card fallback
(new `chartElementToFallbackText` helper).
- Slack adapter: card tables now render as [data table
blocks](https://docs.slack.dev/reference/block-kit/blocks/data-table-block)
by default — paginated and sortable — instead of plain table blocks.
Header-only tables keep the plain table block; tables exceeding Slack
limits (100 data rows, 20 columns, 10,000 characters) fall back to ASCII
as before.
- Slack adapter: card charts render as [data visualization
blocks](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block).
Charts violating Slack constraints (50-character title, 12
segments/series, 20 categories, 20-character labels, one data point per
category, max 2 charts per message) fall back to a text rendering
instead of being rejected by the API.
- The `@chat-adapter/slack/blocks` subpath gets the same treatment:
`SlackChartElement` types, `chart` card children, data table rendering,
and matching limits.
- `postMessage` now surfaces Slack's per-block validation details when
the API rejects blocks (`invalid_blocks`), instead of the bare "An API
error occurred" message.

- 0f743c9: Add declarative agent-experience config and harden native
streaming:

- `suggestedPrompts` — a static payload or per-thread resolver, applied
automatically when an assistant/agent thread opens
(`assistant_thread_started` in legacy `assistant_view`, or a
Messages-tab `app_home_opened` under `agentView`, where prompts pin at
the top of the agent conversation without a `thread_ts`). The resolver
receives the thread context (`channelId`, `userId`, legacy
`threadTs`/`teamId`/`enterpriseId`, and active-view `entities` under
`agentView`); returning `null`/`undefined` skips the thread. Prompts
beyond Slack's 4-prompt limit are dropped with a warning, and
resolver/API failures are logged without failing the webhook.
- `loadingMessages` — default rotating status strings for the assistant
thinking indicator, used by `startTyping` and `setAssistantStatus` when
no explicit status/messages are passed.
- `nativeStreaming` config (default `true`). Set `false` on Slack
flavours without the `chat.startStream` family (e.g. GovSlack) to always
stream via post-and-edit.
- If the workspace rejects the first native streaming call, `stream()`
now falls back to throttled post-and-edit mid-stream instead of failing
the reply; already-consumed text is preserved. Permanent platform errors
(`unknown_method`, `method_deprecated`, `feature_not_enabled`) latch
native streaming off for subsequent streams on the adapter instance.
Structured chunks (`task_update` / `plan_update`) are skipped in
fallback mode.

- `feedbackButtons` — append Slack's native thumbs up/down
(`context_actions` + `feedback_buttons` block) to every streamed reply.
Pass `true` for defaults or an options object (`actionId`, labels,
values); clicks dispatch through `bot.onAction` with a positive/negative
value. A `buildFeedbackButtonsBlock(options?)` helper is exported for
attaching the block to non-streamed messages.

New exported types: `SlackFeedbackButtonsOptions`,
`SlackSuggestedPrompt`, `SlackSuggestedPrompts`,
`SlackSuggestedPromptsContext`, `SlackSuggestedPromptsOptions`.

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/telegram@4.34.0

### Minor Changes

- 8d7ccdb: Add Telegram media group uploads for multiple files and
attachments.

### Patch Changes

- 2531a42: Fix `detectMention` (and the Telegram adapter's
`isBotMentioned`) falsely matching `@bot` when `@bot-dev` is mentioned.
`\b` (word boundary) matches between a word character and a hyphen, so
`/@bot\b/` incorrectly matches `@bot-dev`. Replaced with `(?![\w-])` to
exclude hyphens.
- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/web@4.34.0

### Minor Changes

- 6714efc: Support AI SDK v7 as a peer dependency.

- `chat` now accepts `ai@^6.0.182 || ^7.0.0` (`chat/ai` tools work with
both majors).
- `@chat-adapter/web` now accepts `ai@^6 || ^7`, `@ai-sdk/react@^3 ||
^4`, `@ai-sdk/svelte@^4 || ^5`, and `@ai-sdk/vue@^3 || ^4`.
- The `chat/ai` tool factories now declare explicit `Tool<Input,
Output>` return types instead of relying on inference, so the published
declarations no longer depend on `ai` internals that changed in v7. The
public type surface is unchanged.

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/whatsapp@4.34.0

### Minor Changes

- 2338a66: Add `sendTemplate()` for sending pre-approved template
messages, enabling business-initiated conversations outside the 24-hour
customer service window
- 8bd8a57: Implement outbound file and attachment sending for the
WhatsApp adapter.

Supports binary `FileUpload` uploads, typed `Attachment` payloads
(binary or HTTPS link passthrough), multi-file sequential sends, smart
MIME-to-message-type mapping, caption placement with audio/long-text
fallbacks, and card+file sequencing.

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/x@4.34.0

### Minor Changes

- 4bca64f: add image upload support to X posts and DMs via the chunked
media upload endpoints

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## chat@4.34.0

### Minor Changes

- 1721fa0: Add support for Slack's Agent messaging experience
(`agent_view`).

- New core event `onAppContextChanged` with a normalized
`AppContextEntity[]` describing the user's active view (channel / canvas
/ list / message / unknown).
- `AppHomeOpenedEvent` now carries the folded active-view context as
`entities` and the opened `tab` (Slack: `"home"` / `"messages"`), so
handlers can tell a Home-tab open from the DM-open signal under
`agent_view`.
- Slack adapter: new `agentView` config flag (under `agent_view`,
`app_home_opened` is the DM-open signal regardless of tab and folded
context is surfaced), routing for the `app_context_changed` event, and a
`getAppContext(message)` helper to read the folded context on DM
messages.
- `setSuggestedPrompts` now accepts an optional thread reference
(agent_view lets prompts sit at the top of the agent conversation).
- Under `agentView`, DM (Messages-tab) messages are threaded per new
Slack's model — each user message is a thread root (`thread_ts ?? ts`).
Conversation-scoped threads returned by `openDM()` keep working: when
that thread is subscribed, incoming top-level DM messages route to it.
- `createSlackAdapter` env auth fallback (`SLACK_BOT_TOKEN` /
`SLACK_CLIENT_ID` / `SLACK_CLIENT_SECRET`) is now disabled only when an
auth or verification field (`botToken`, `clientId`, `clientSecret`,
`installationProvider`, `signingSecret`, `webhookVerifier`) is passed
explicitly, instead of by any config object — so `createSlackAdapter({
agentView: true })` still picks up env credentials, while
explicit-secret configs stay immune to ambient env vars.
- Bumped `@slack/web-api` to `^7.18.0` (adds the optional `thread_ts`
typing for `setSuggestedPrompts`).

- 4717a38: Add chart support and richer table rendering, with native
Slack data table and data visualization blocks.

- New core `ChartElement` and `Chart()` builder (JSX supported) with
pie, bar, area, and line charts, mirroring Slack's data visualization
model: pie charts take `segments`, series charts take named `series`
plotted against shared `categories` with optional `xLabel`/`yLabel`.
- `TableElement` / `Table()` gain optional `caption` (accessible table
description) and `pageSize` (rows per page) fields.
- Charts degrade gracefully on platforms without native chart support:
the underlying data renders as a text table via the shared card fallback
(new `chartElementToFallbackText` helper).
- Slack adapter: card tables now render as [data table
blocks](https://docs.slack.dev/reference/block-kit/blocks/data-table-block)
by default — paginated and sortable — instead of plain table blocks.
Header-only tables keep the plain table block; tables exceeding Slack
limits (100 data rows, 20 columns, 10,000 characters) fall back to ASCII
as before.
- Slack adapter: card charts render as [data visualization
blocks](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block).
Charts violating Slack constraints (50-character title, 12
segments/series, 20 categories, 20-character labels, one data point per
category, max 2 charts per message) fall back to a text rendering
instead of being rejected by the API.
- The `@chat-adapter/slack/blocks` subpath gets the same treatment:
`SlackChartElement` types, `chart` card children, data table rendering,
and matching limits.
- `postMessage` now surfaces Slack's per-block validation details when
the API rejects blocks (`invalid_blocks`), instead of the bare "An API
error occurred" message.

- 6714efc: Support AI SDK v7 as a peer dependency.

- `chat` now accepts `ai@^6.0.182 || ^7.0.0` (`chat/ai` tools work with
both majors).
- `@chat-adapter/web` now accepts `ai@^6 || ^7`, `@ai-sdk/react@^3 ||
^4`, `@ai-sdk/svelte@^4 || ^5`, and `@ai-sdk/vue@^3 || ^4`.
- The `chat/ai` tool factories now declare explicit `Tool<Input,
Output>` return types instead of relying on inference, so the published
declarations no longer depend on `ai` internals that changed in v7. The
public type surface is unchanged.

### Patch Changes

- 5c926f1: Preserve markdown structural whitespace when extracting
normalized message text.
- 2531a42: Fix `detectMention` (and the Telegram adapter's
`isBotMentioned`) falsely matching `@bot` when `@bot-dev` is mentioned.
`\b` (word boundary) matches between a word character and a hyphen, so
`/@bot\b/` incorrectly matches `@bot-dev`. Replaced with `(?![\w-])` to
exclude hyphens.
## @chat-adapter/gchat@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/github@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/linear@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/messenger@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/shared@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
## @chat-adapter/teams@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/twilio@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
  - @chat-adapter/shared@4.34.0
## @chat-adapter/state-ioredis@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
## @chat-adapter/state-memory@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
## @chat-adapter/state-pg@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
## @chat-adapter/state-redis@4.34.0

### Patch Changes

- Updated dependencies [5c926f1]
- Updated dependencies [2531a42]
- Updated dependencies [1721fa0]
- Updated dependencies [4717a38]
- Updated dependencies [6714efc]
  - chat@4.34.0
## @chat-adapter/tests@4.34.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-13 16:41:59 +01:00
Remon Oldenbeuving 5c926f1987 fix(chat): preserve markdown whitespace in plain text (#604)
## Problem
When I mention a Chat SDK-powered bot on GitHub like this:

```
@bot
hi there!
```

The mention doesn't trigger any handling, because the `mdastToString`
helper replaces all newlines with an empty string, so the logs look like
this:

```
[chat-sdk] Checking message patterns { patternCount: 0, patterns: [], messageText: '@bothi there' }
[chat-sdk] No handlers matched message {
  threadId: 'github:RSO/chat:issue:29',
  text: '@bothi there'
}
```

## Summary
- Preserve structural markdown whitespace when extracting normalized
plain text from mdast.
- Add regression coverage for newline-separated bot mentions in core
markdown extraction, GitHub issue/review comments, and Chat mention
routing.
- Add a patch changeset for the behavior fix.

## Testing
- `pnpm check`
- `pnpm knip`
- `pnpm test:workspace`
- `pnpm --filter chat test`
- `pnpm --filter @chat-adapter/github test`
- `pnpm --filter chat typecheck`
- `pnpm --filter @chat-adapter/github typecheck`

## Notes
- `pnpm validate` was attempted and reached the full Turbo test graph,
but failed on `packages/integration-tests/src/replay-discord.test.ts`
(`should skip bot's own messages in subscribed threads`). Rerunning
`pnpm --filter @chat-adapter/integration-tests test --
src/replay-discord.test.ts` passed, so this appears unrelated to the
markdown whitespace change.
- `.github/CONTRIBUTING.md` requires signed commits. This environment
has no `gpg` binary and no SSH signing identities loaded, so the commit
in this PR is currently unsigned and may need to be re-signed before
merge.

---------

Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-13 13:40:17 +01:00
C. T. Lin 6714efc3a1 feat: support AI SDK v7 (ai@7) as a peer dependency (#691)
Closes #690

## What

Widens the AI SDK peer dependency ranges so the Chat SDK installs
cleanly next to `ai@7`:

- `chat`: `ai@^6.0.182 || ^7.0.0`
- `@chat-adapter/web`: `ai@^6 || ^7`, `@ai-sdk/react@^3 || ^4`,
`@ai-sdk/svelte@^4 || ^5`, `@ai-sdk/vue@^3 || ^4`

This also unbreaks `create-chat-sdk` scaffolds, which install
`ai@latest` (now v7) next to `chat` and currently hit a peer conflict
out of the box.

## The one real v6 → v7 break

In v7, `tool()` with an `execute` function returns
`ExecutableTool<Tool<...>>` — an internal type from
`@ai-sdk/provider-utils` that `ai` does not re-export. The `chat/ai`
tool factories relied on inference, so declaration emit failed with
TS2742 (17 errors). The factories now declare explicit `Tool<Input,
Output>` return types, which is exactly the shape the previously
published `.d.ts` already had — the public type surface is unchanged,
and the emitted declarations only reference types from `ai` (portable
for consumers on either major).

Everything else checked out compatible:

- v7 stream parts keep `text-delta` / `finish-step` shapes, so
`fromFullStream` duck-typing works unchanged; `fullStream` remains as a
deprecated alias
- tool-level `needsApproval` is deprecated in v7 but still typed and
honored
- `createUIMessageStream`, `createUIMessageStreamResponse`,
`isTextUIPart`, `UIMessage`, `UIMessageStreamWriter`, `ChatInit`,
`DefaultChatTransport` all still exported — `@chat-adapter/web` needed
zero source changes

## Other changes

- devDependencies move to v7 so the workspace develops/tests against the
latest major
- `examples/nextjs-chat` and `examples/nuxt-chat` move to `ai@^7`
(required — mixing majors across the workspace fails typecheck, since
`chat`'s d.ts resolves `ai` types from its own devDependency)
- Test-only: the `ToolExecutionOptions` stub type is now derived from
`Tool["execute"]` because v7 made the generic parameter required
- Changeset included (minor for `chat` and `@chat-adapter/web`)

## Verification

The same source was verified against **both majors** (`ai@6.0.182` and
`ai@7.0.17`): `tsc --noEmit` and the full test suites (`chat`: 1035
tests, `@chat-adapter/web`: 21 tests) pass on each. `pnpm validate`
(knip + check + typecheck + test + build, including both examples) is
green on v7.

Note for adopters: `ai@7` itself requires Node.js ≥ 22 and is ESM-only;
`chat` keeps `engines.node >= 20` since `ai` is an optional peer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: chentsulin <chentsulin@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-13 21:50:27 +10:00
Shkumbin Hasani 2338a66544 feat(whatsapp): add sendTemplate for pre-approved template messages (#588)
Closes #585

## Summary

Adds `sendTemplate()` to the WhatsApp adapter for sending pre-approved
[Message
Templates](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-message-templates)
— the only message type the Cloud API accepts outside the 24-hour
customer service window, and therefore required for business-initiated
conversations (notifications, reminders, re-engagement).

The inbound half already existed (`handleButtonResponse` dispatches
template quick-reply taps to `onAction` handlers); this completes the
outbound side. Notably, the package's `AGENTS.md` already documented
`sendTemplate` and `WhatsAppTemplateMessage` as part of the public
surface — this PR implements exactly that documented API.

## Changes

- **`sendTemplate(threadId, template)`** on `WhatsAppAdapter` — posts a
`type: "template"` payload through the existing `graphApiRequest` path
and returns a `RawMessage`, mirroring
`sendInteractiveMessage`/`sendSingleTextMessage`
- **Types**: `WhatsAppTemplateMessage`, `WhatsAppTemplateComponent`,
`WhatsAppTemplateParameter`, `WhatsAppTemplateButtonParameter` in
`types.ts`, modeled on the Cloud API template object (header/body/button
components; text, currency, date_time, and media parameters),
re-exported from the package entry point
- Templates are kept out of the `PostableMessage`/mdast pipeline —
they're sent by name + variable components, not free-form markdown, and
the adapter intentionally does not auto-substitute templates for
outbound text posts (per AGENTS.md)
- `openDM()` JSDoc now links to `sendTemplate` for the
business-initiated path

## Docs

- New "Template messages" section in the adapter README and
`apps/docs/content/adapters/official/whatsapp.mdx` with a usage example
- Added "Template messages" row to the README feature table
- Corrected the feature-matrix frontmatter labels: `cardFormat`
"WhatsApp templates" → "Interactive messages" and `fields` "Template
variables" → "Formatted text" — cards render as Cloud API interactive
messages (as the page body already states), and the old labels would now
wrongly imply cards go through the new template API

## Usage

```typescript
const threadId = await adapter.openDM("15551234567");

await adapter.sendTemplate(threadId, {
  name: "appointment_reminder",
  language: "en",
  components: [
    {
      type: "body",
      parameters: [{ type: "text", text: "Tomorrow at 2pm" }],
    },
  ],
});
```

## Testing

- 5 new tests in `index.test.ts` following the existing `fetch`-spy
pattern: payload shape (name/language/`to`), component pass-through
(body + URL button), empty-components omission, missing-message-ID
error, and invalid thread ID rejection
- `pnpm validate` (knip, check, typecheck, test, build) passes — 117/117
adapter tests green

Includes a `minor` changeset for `@chat-adapter/whatsapp`.

---------

Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-13 21:50:06 +10:00
josh 57fa85ca54 test(telegram): raise rich-truncate test timeout to 15s (#703)
## summary

the `rich markdown messages truncate at the rich message limit` test
runs ~5.5s in CI (coverage + shared runner) and intermittently exceeds
the 5s default, failing unrelated PRs. raise its timeout to 15s

## test plan

- `pnpm --filter @chat-adapter/telegram test`

Signed-off-by: dancer <josh@afterima.ge>
2026-07-13 12:49:12 +01:00
Ben Sabic 0f743c9b33 feat(slack): support native Slack agents (#698)
Builds on the Agent messaging experience support from #684 with a
declarative config layer for building Slack agents, plus hardening for
native streaming.

Everything is configured on `createSlackAdapter()` — no per-event wiring
required.

## Slack adapter (`@chat-adapter/slack`)

### `suggestedPrompts`

Static payload or per-thread resolver, applied automatically when an
assistant/agent thread opens:

- `assistant_thread_started` (legacy `assistant_view`), with the
thread's `thread_ts`
- Messages-tab `app_home_opened` (with `agentView` enabled), without
`thread_ts` so prompts pin atop the agent conversation

The resolver receives the thread context (`channelId`, `userId`, legacy
`threadTs`/`teamId`/`enterpriseId`, and normalized active-view
`entities` under `agentView`); returning `null`/`undefined` skips the
thread. Prompts beyond Slack's 4-prompt limit are dropped with a
warning. Resolver/API failures are logged, never a webhook 500. Applied
via `waitUntil` inside the request scope so multi-workspace token
context propagates.

### `loadingMessages`

Default rotating status strings for the assistant thinking indicator,
used by `startTyping` and `setAssistantStatus` when no explicit
status/messages are passed.

### `nativeStreaming` + automatic post-and-edit fallback

- New `nativeStreaming` config (default `true`). Set `false` on Slack
flavours without the `chat.startStream` family (e.g., GovSlack) to
always stream via post-and-edit.
- If the workspace rejects the **first** native streaming call,
`stream()` falls back to throttled post-and-edit mid-stream instead of
failing the reply; already-consumed text is preserved (it lives in the
renderer). Permanent platform errors (`unknown_method`,
`method_deprecated`, `feature_not_enabled`) latch native streaming off
for subsequent streams on the adapter instance; transient errors don't
latch.
- Structured chunks (`task_update`/`plan_update`) are skipped in
fallback mode; failures after native content has rendered still
propagate (mixing surfaces would duplicate output).
- Also updates the stale streaming description in the package AGENTS.md
(the adapter now streams via
`chat.startStream`/`appendStream`/`stopStream`, not `chat.update`).

### `feedbackButtons`

Appends Slack's native thumbs up/down (a `context_actions` block with a
`feedback_buttons` element) to every streamed reply on
`chat.stopStream`, after any `StreamingPlan` `endWith` blocks. Pass
`true` for defaults or an options object (`actionId`, labels, values).
Clicks dispatch through the regular `block_actions` flow to
`bot.onAction` with a positive/negative value — no new plumbing. Exports
`buildFeedbackButtonsBlock(options?)` for attaching the same block to
non-streamed messages.

New exported types: `SlackFeedbackButtonsOptions`,
`SlackSuggestedPrompt`, `SlackSuggestedPrompts`,
`SlackSuggestedPromptsContext`, `SlackSuggestedPromptsOptions`.

## Docs

- Configuration table rows for `agentView`, `suggestedPrompts`,
`loadingMessages`, `nativeStreaming`, `feedbackButtons`.
- New "Native streaming" and "Feedback buttons" sections plus
declarative suggested-prompts examples.
- All agent content grouped under a new **Advanced → Agents** subsection
(Agent messaging experience → Assistants API → Native streaming →
Feedback buttons). Heading titles unchanged, so existing anchors keep
resolving.
- TypeTable descriptions rewritten as plain text (they don't render
markdown).

## Example app (`examples/nextjs-chat`)

- `SLACK_AGENT_OPTIONS` shared across both Slack adapter branches:
active-view-aware `suggestedPrompts` resolver, `loadingMessages`,
`feedbackButtons` with an `ai_feedback` acknowledgment handler.
- Env toggles: `SLACK_AGENT_VIEW` (agent_view mode) and
`SLACK_NATIVE_STREAMING` (compare native vs post-and-edit).
- Commented `agent_view` blocks in `slack-manifest.yml` (feature block,
`assistant:write` scope, agent events) with a note that the switch is
irreversible.
- AI flows call `startTyping()` without an explicit status so configured
loading messages rotate.

## Test plan

- `pnpm validate` and `pnpm konsistent` pass.
- 26 new unit tests: suggested prompts
(static/resolver/agent_view/truncation/error paths), loading message
defaults, native streaming fallback (opt-out, mid-stream fallback,
permanent-error latching, transient non-latching, propagation after
native render, structured-chunk skipping), feedback buttons (block
shape, custom options, ordering after `endWith`, webhook round-trip of a
click).
- Each adapter commit was built and verified independently (typecheck +
full suite green at every step) for bisectability.
- Verified manually against a live `agent_view` workspace: prompts
pinned on thread open, loading messages rotating in the thinking
indicator, native token-by-token streaming in DMs and channel threads,
post-and-edit fallback via the opt-out flag, and feedback clicks
dispatching to `onAction`.

## Notes

- One changeset covers the three adapter features (`minor` for
`@chat-adapter/slack`).
- Known follow-up (not in this PR): under `agentView`, a subscribed
conversation-scoped DM thread (the #684 openDM bridge) routes DM
messages to a thread without `thread_ts`, which silently pins DMs to
post-and-edit. Worth deciding whether the bridge should keep per-message
threading for replies or log loudly when it redirects.

## Screenshots

| Suggested Prompts | Feedback Buttons |
| --- | --- |
| <img width="647" height="347" alt="CleanShot 2026-07-13 at 13 26 27"
src="https://github.com/user-attachments/assets/4c89932b-ed19-4b4a-83ae-d3d022d0c120"
/> | <img width="825" height="276" alt="CleanShot 2026-07-13 at 13 28
23"
src="https://github.com/user-attachments/assets/6bdf6979-5209-4bed-b0bc-dfbee7165455"
/> |

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-13 12:27:25 +01:00
Dima Grossman 8bd8a57518 feat(whatsapp): send outbound files and attachments via Cloud API (#537)
Implement media upload, MIME mapping, caption fallbacks, and card+file
sequencing.

## Summary

The WhatsApp adapter previously ignored `files` and `attachments` on
outbound `post()` calls (only text and interactive cards were sent).
This PR implements full outbound media support via the [WhatsApp Cloud
API](https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media):

1. **Binary upload** — `POST /{phoneNumberId}/media` → `media_id` →
typed media message
2. **Link passthrough** — HTTPS `Attachment.url` sent directly (no
upload)
3. **Multi-file** — one WhatsApp message per file/attachment, sent
sequentially
4. **Captions** — markdown or card fallback text on the first media
message when supported
5. **Card + files** — media first, then interactive buttons (when
applicable)

**Packages:** `@chat-adapter/whatsapp` (minor)

---

## Supported inputs

| Input | Description |
|-------|-------------|
| `files: FileUpload[]` | Binary buffers/blobs with `filename` and
optional `mimeType`. Always uploaded via `/media`. |
| `attachments: Attachment[]` | Typed media (`image` \| `file` \|
`video` \| `audio`). Binary via `data` / `fetchData`, or HTTPS URL-only
via `url`. |

Both can be combined on `{ markdown }`, `{ raw }`, `{ ast }`, or `{ card
}` postables. `files` are processed first, then `attachments`.

### Examples

```typescript
// PDF with caption
await thread.post({
  markdown: "Here's the report",
  files: [{ data: pdfBuffer, filename: "report.pdf", mimeType: "application/pdf" }],
});

// Multiple files (N sequential messages)
await thread.post({
  markdown: "Two files attached",
  files: [
    { data: buf1, filename: "a.pdf", mimeType: "application/pdf" },
    { data: buf2, filename: "b.png", mimeType: "image/png" },
  ],
});

// Card with buttons + image file
await thread.post({
  card: approvalCard,
  files: [{ data: proofBuffer, filename: "proof.png", mimeType: "image/png" }],
});

// Files only (no text)
await thread.post({
  markdown: "",
  files: [{ data: buffer, filename: "data.xlsx" }],
});
```

---

## Behavior reference

### Message flow (with media)

```
postMessage()
  ├─ files or attachments present?
  │    YES → postMessageWithMedia()
  │         ├─ Resolve text (card fallback OR markdown/raw/ast)
  │         ├─ Caption strategy (see below)
  │         ├─ For each file/attachment: upload (if binary) → sendMediaMessage()
  │         └─ Card present?
  │              ├─ interactive buttons → sendInteractiveMessage()
  │              └─ text-only card fallback → sendTextMessage() (if caption didn't already send text)
  │
  └─ NO → existing text / card-only path (unchanged)
```

### Multi-file

WhatsApp allows **one media object per API message**. Multiple `files`
or `attachments` in a single `post()` produce **N sequential messages**.
The returned `RawMessage` is the **last** one sent (same convention as
long-text chunking).

| File index | Caption |
|------------|---------|
| First | Markdown / card fallback text (when caption rules allow) |
| 2…N | No caption |

### Caption placement

| Condition | Behavior |
|-----------|----------|
| Text ≤ 1024 chars, first media is not `audio`, media supports captions
| Text sent as **caption** on first media message |
| Text > 1024 chars | **Separate text message first**, then media with
no captions |
| First media is `audio` | **Separate text message first** (audio does
not support captions), then audio |
| No text (`markdown: ""`, files only) | Media only, no caption |

### MIME type → WhatsApp message type

| MIME | WhatsApp `type` |
|------|-----------------|
| `image/jpeg`, `image/png` | `image` |
| Other `image/*` (e.g. GIF, WebP, SVG) | `document` |
| `video/mp4`, `video/3gpp` | `video` |
| `audio/*` | `audio` |
| Everything else (PDF, XLSX, etc.) | `document` |

For `Attachment` without `mimeType`, the adapter uses `attachment.type`
(`image` → image, `file` → document, etc.), then applies MIME rules when
`mimeType` is set.

### Size limits (pre-flight)

Throws `ValidationError` when binary size is known (before upload):

| Type | Limit |
|------|-------|
| `image` | 5 MB |
| `audio` | 16 MB |
| `video` | 16 MB |
| `document` | 100 MB |

URL-only attachments skip size validation unless `attachment.size` is
provided.

### Card + files

When both a **card** and **files/attachments** are present:

1. **Media message(s)** first — caption uses `cardToFallbackText(card)`
on the first media item
2. **Card message** second:
   - Valid reply buttons (1–3) → interactive button message
- Otherwise → text fallback message (skipped if text was already sent as
a leading message)

### Card image vs `files` (important)

| How image is provided | Result |
|-----------------------|--------|
| `<Image>` child or `card.imageUrl` only (no `files`) | **No real image
media.** Card becomes interactive text or text fallback; image URL may
appear as plain text in fallback. |
| `files` / `attachments` + card with buttons | **Real image message** +
separate interactive button message |

To send a photo with buttons, pass the image via `files` or
`attachments`, not only as a card image child.

### Link passthrough

- `Attachment` with **only** `url` (no `data` / `fetchData`) → `{ link:
url }` in the media payload
- URL **must** be `https://`
- No `/media` upload call

### Binary resolution

| Source | Path |
|--------|------|
| `FileUpload.data` | `toBuffer()` → `uploadMedia()` → `{ id }` |
| `Attachment.data` / `fetchData` | Same |
| `Attachment.url` only | `{ link }` passthrough |

---

## Out of scope (follow-ups)

- Stickers (WebP encoding requirements)
- Voice notes (`voice` vs `audio` distinction)
- Media ID caching across posts (30-day expiry)
- Interactive message **image headers** (card-embedded images without
`files`)
- Replay integration test mock extensions for `/media`
- Edit/replace flows that include files

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-13 21:03:07 +10:00
Takuma Shibuya 2531a4227e fix username regexp (#621)
## Summary

<!-- What does this PR do? -->

Fix `detectMention` falsely matching `@bot` when `@bot-dev` is
mentioned. `\b` (word boundary) matches between a word character and a
hyphen, so `/@bot\b/` incorrectly matches `@bot-dev`. Replaced with
`(?![\w-])` to exclude hyphens.

## Test plan

<!-- How did you verify the changes? -->

## Checklist

- [x] All commits are signed and verified
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — see
[CONTRIBUTING.md](./CONTRIBUTING.md))
- [x] Documentation updated (or N/A)

---------

Signed-off-by: sivchari <shibuuuu5@gmail.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-13 20:39:20 +10:00
josh 4bca64f058 feat(x): support image uploads on posts and DMs (#700)
## summary

the X adapter previously rejected every attachment (`File uploads are
not supported by the X adapter yet`). this adds image upload support:
images passed as `files` or `attachments` are uploaded through X's v2
chunked media endpoints and attached to the resulting post or DM

- uploads via X's current path-based flow: `POST
/2/media/upload/initialize` (JSON) then `/{id}/append` (multipart) then
`/{id}/finalize`, reusing the adapter's managed OAuth token
- attaches `media_ids` on `POST /2/tweets` for posts, and `attachments`
for DMs
- supports png, jpeg, and webp, up to 4 per post, with or without text
- requires the `media.write` OAuth 2.0 scope on the user token

### before / after

- before: posting a message with `files`/`attachments` throws a
`ValidationError`
- after: images upload and attach, and a post can be media-only or media
plus text

<details>
<summary>usage</summary>

```typescript
await thread.post({
  markdown: "France lead the title race",
  files: [{ data: pngBuffer, filename: "odds.png", mimeType: "image/png" }],
});
```

</details>

## test plan

- added unit tests covering the initialize JSON body, the multipart
append path, finalize, `media_ids` on the tweet, DM `attachments`,
media-only posts, MIME inference from filename, the over-limit
rejection, and unsupported-type rejection
- verified live against the X API end to end: uploaded an image and
posted then deleted it through the adapter (the initial command-param
implementation 400'd against the live API, which is what surfaced the
path-based endpoints as required)
- `pnpm --filter @chat-adapter/x build`, tests, `pnpm exec biome check`,
and `pnpm konsistent` all pass

---------

Signed-off-by: dancer <josh@afterima.ge>
2026-07-13 18:57:29 +10:00
ishaksebsib 8d7ccdb11b feat(telegram): support multiple file and attachment uploads (#605)
## Summary

Adds Telegram media group support for posting multiple files and
compatible typed attachments. Multiple `files` are sent as document
media groups, while `attachments` preserve image, video, audio, or file
media types and use Telegram’s native `sendMediaGroup`.

---------

Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-13 08:59:49 +01:00
Dean Mauro 0fdb902980 feat(discord): Components support (#678)
## Summary

#### What

Adds opt-in support for Discord
[Components](https://docs.discord.com/developers/components/reference).

#### Why

Discord Components allows developers more control over the layout of bot
messages by treating text, images, files, and buttons as flexible
components. Instead of the rigid text-above-embeds layout, elements can
be arranged in any order or column.

An extreme example of what's possible with Components:

<img width="728" height="1117" alt="image"
src="https://github.com/user-attachments/assets/fde6ab9c-f635-45fb-b64f-9ddf4c26580f"
/>

#### How

Embeds remain the default behavior. The Discord adapter now supports a
`componentsV2` flag to
When enabled, card messages render with Discord Components v2
containers, sections, text displays, media galleries, separators,
buttons, and string selects, and include the `IS_COMPONENTS_V2` message
flag.

## Test plan

Create a Chat with the Discord adapter. Set `contentFormat:
DiscordContentFormat.ComponentsV2` and create a post that uses sections,
markdown, buttons, etc. Note that
1. All elements should render correctly.
2. Individual sections can contain their own actions.
3. Markdown formatting gets rendered correctly.

```
import { Actions, Button, Card, CardText, Image, LinkButton, Section } from "chat";
import { createDiscordAdapter } from "@chat-adapter/discord";

const discord = createDiscordAdapter({
  contentFormat: DiscordContentFormat.ComponentsV2,
});

await thread.post(
  <Card title="Deployment ready" subtitle="Production build completed">
    <Section>
      <CardText>
        **Version 2.4.0** is ready to promote.

        Review the release notes, then choose an action below.
      </CardText>
      <Image url="https://example.com/deploy-preview.png" alt="Preview" />
    </Section>

    <Actions>
      <Button id="promote" style="primary">
        Promote
      </Button>
      <Button id="rollback" style="danger">
        Roll back
      </Button>
      <LinkButton url="https://example.com/deployments/123">
        View deployment
      </LinkButton>
    </Actions>
  </Card>
);
```

---------

Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-13 08:48:48 +01:00
Max 6c2a3918d5 feat(discord): rename thread channels (#693)
## Summary

Add `DiscordAdapter#setThreadTitle()` to rename native Discord thread
channels and document the required **Manage Threads** permission.

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-07-13 08:17:21 +01:00
Faraz Patankar 5341f909a4 feat(discord): ignore @everyone/@here pings unless respondToGlobalMentions is set (#701)
## Summary

Fixes #699.

In gateway mode, the bot responded to `@everyone`/`@here` announcements.
The legacy gateway listener used discord.js
`message.mentions.has(botId)`, which counts global pings as a mention of
the bot by default.

Global pings are now ignored on both gateway paths by default, and a new
`respondToGlobalMentions` config option (default `false`, per the
issue's suggestion) lets users opt back in:

- **Legacy gateway mode**: mention detection passes `{ ignoreEveryone:
true }` to `mentions.has()`; `@everyone`/`@here` only count via
`message.mentions.everyone` when the option is enabled. Direct mentions,
`mentionRoleIds` role mentions, and replies to the bot are unaffected.
- **Forwarded gateway mode**: added the `mention_everyone` field to
`DiscordGatewayMessageData` and wired it into the same gate (this path
previously ignored global pings silently, with no way to opt in).

## Changes

- `respondToGlobalMentions?: boolean` on `DiscordAdapterConfig` (default
`false`)
- Mention detection updated in both `setupLegacyGatewayHandlers` and
`handleForwardedMessage`
- 5 new tests covering both transports: global pings ignored by default,
honored when opted in, and direct mentions still detected while global
pings are ignored
- Docs: config tables in
`apps/docs/content/adapters/official/discord.mdx` and the adapter README
- Changeset (minor, `@chat-adapter/discord`)

## Testing

- `pnpm validate` passes (knip + check + typecheck + test + build)
- All 249 `@chat-adapter/discord` tests pass, including the 5 new ones

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Faraz Patankar <farazpatankar@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 08:12:43 +01:00
Ben Sabic 4717a38407 feat(slack): support data table and data visualization blocks (#696)
Adds support for Slack's [data
table](https://docs.slack.dev/reference/block-kit/blocks/data-table-block)
and [data
visualization](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block)
Block Kit blocks.

- **`chat`**: new cross-platform `ChartElement` + `Chart()` builder (JSX
supported) mirroring Slack's model — pie `segments`, or bar/area/line
`series` against shared `categories`. `Table()` gains optional `caption`
and `pageSize`. Charts degrade to a text table on other platforms via
the shared card fallback (`chartElementToFallbackText`).
- **`@chat-adapter/slack`**: card tables now render as paginated,
sortable `data_table` blocks by default (header-only tables keep the
plain `table` block; oversized tables still fall back to ASCII). Charts
render as `data_visualization` blocks; charts violating Slack
constraints — including the undocumented **max 2 charts per message** —
fall back to a text rendering instead of an API rejection. Same
treatment in the `@chat-adapter/slack/blocks` subpath.
- **`postMessage`** now surfaces Slack's per-block validation messages
on `invalid_blocks` errors (this is how the 2-chart limit was found).
- Example app gets a **Show Charts** button and table pagination on
**Show Table**; docs, feature matrices, and changeset updated.

Verified live against Slack: data table pagination/sorting and both
chart types render natively.

<table>
  <tr>
    <th>Data Table</th>
    <th>Data Charts</th>
  </tr>
  <tr>
<td><img width="979" height="896" alt="CleanShot 2026-07-12 at 23 28 32"
src="https://github.com/user-attachments/assets/3307bd90-9322-452f-86fb-07d46446822d"
/></td>
<td><img width="955" height="879" alt="CleanShot 2026-07-12 at 23 29 02"
src="https://github.com/user-attachments/assets/ddb31a1b-e3fd-457c-a2e6-bde4934afebe"
/></td>
  </tr>
</table>

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-13 07:57:40 +01:00
damianborowy-nexos 1721fa01e7 feat(slack): add Slack Agent messaging experience (agent_view) support (#684)
## Summary

Add support for Slack's Agent messaging experience (`agent_view`), the
2026 replacement for `assistant_view`.

## Core (`chat`)

- New `onAppContextChanged` event carrying the active-view context as a
normalized `AppContextEntity[]` (`channel` / `canvas` / `list` /
`message` / `unknown`) describing what the user is currently viewing.
- `AppHomeOpenedEvent` now carries:
  - the same folded active-view context as optional `entities`
- the opened `tab` (`"home"` / `"messages"`), so handlers can
distinguish a Home-tab open from the DM-open signal under `agent_view`

## Slack adapter (`@chat-adapter/slack`)

- **`agentView` config flag.** Under `agent_view`:
- `app_home_opened` is the DM-open signal and fires regardless of tab
(branch on `event.tab` if you also publish a Home view)
- DM messages are threaded per Slack's new model — each user message is
a thread root (`thread_ts ?? ts`)
- conversation-scoped threads returned by `openDM()` keep working: when
that thread is subscribed, incoming top-level DM messages route to it,
so `onSubscribedMessage` and per-thread state behave the same as in
legacy mode
- **`app_context_changed` routing** with normalized entities. Malformed
payloads degrade gracefully: a missing `context` yields `entities: []`,
and entities with a null/malformed `value` normalize to `kind:
"unknown"` — never a webhook 500.
- **`getAppContext(message)`** helper to read the folded active-view
context off a DM message.
- **`setSuggestedPrompts`** accepts an optional thread reference
(`agent_view` lets prompts sit at the top of the agent conversation).
- **Env auth fallback now keys off auth fields**: `SLACK_BOT_TOKEN` /
`SLACK_CLIENT_ID` / `SLACK_CLIENT_SECRET` fallback is disabled only when
an auth-related field (`botToken`, `clientId`, `clientSecret`,
`installationProvider`) is passed explicitly, rather than by the
presence of any config object. This lets non-auth options compose with
env auth — e.g. `createSlackAdapter({ agentView: true })` picks up env
credentials — and matches the semantics documented in the adapter's
AGENTS.md. *(Behavior change for callers passing non-auth-only configs
while relying on env vars being ignored.)*
- Bumped `@slack/web-api` to `^7.18.0` (adds the optional `thread_ts`
typing for `setSuggestedPrompts`).

## Docs

- New "Agent messaging experience" section on the Slack adapter page
(config, manifest snippet, threading model, openDM bridge).
- "Handling active-view context" section in handling-events, plus
`tab`/`entities` rows on the app-home event table.
- Callout: under `agent_view`, bot replies are threaded per user
message, so `conversations.history` only returns the user's side of a DM
— build AI conversation history from transcripts instead of channel
history.

## Example app (`examples/nextjs-chat`)

- Plain `SLACK_BOT_TOKEN` adapter branch (previously Slack was only
wired via Vercel Connect).
- DM AI history built from transcripts instead of channel history (see
docs callout above); assistant turns persisted.
- The `dm me` trigger regex now matches mention text, which carries the
`@bot` prefix on Slack.

## Test plan

- `pnpm validate` and `pnpm konsistent` pass.
- Unit tests cover the new events, entity normalization (including
malformed payloads), `agent_view` DM threading, the openDM subscription
bridge, `tab` passthrough, `setSuggestedPrompts` thread handling, and
env-fallback behavior; an integration replay test exercises the full
webhook flow.
- Verified manually against a live `agent_view` workspace:
`onAppContextChanged` entities, folded context on `app_home_opened` and
DM messages, `tab` values for both tabs, per-message DM threading, the
openDM subscription bridge, and signed malformed-payload replays (all
return 200).
- Legacy regression pass with `agentView` off: conversation-scoped DM
threading, Home-tab-only `app_home_opened`, mention flow unchanged.

### Slack references

- Agent messaging experience:
https://docs.slack.dev/changelog/2026/06/30/agent-messages-tab/
- Active-view context:
https://docs.slack.dev/changelog/2026/07/02/app-context/

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added
- [x] Documentation updated

---------

Signed-off-by: Damian Borowy <301205838+damianborowy-nexos@users.noreply.github.com>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-12 19:35:28 +10:00
Rich Haines 1dff4515e2 refactor(docs): migrate chat-sdk.dev to @vercel/geistdocs (#686)
## Summary

Migrates `apps/docs` from locally-copied geistdocs runtime code to the
published
[`@vercel/geistdocs`](https://www.npmjs.com/package/@vercel/geistdocs)
package (1.8.2), following the official [migration
guide](https://preview.geistdocs.com/docs/migration). Net **−8,400
lines**.

### Package-backed now

- Docs page + layouts: `createDocsPage`, `GeistdocsDocsLayout`,
`GeistdocsHomeLayout` (JSON-LD + sr-only markdown hints preserved via
`renderTop`)
- Navbar (OSS product switcher via `navbarOssProducts`), footer,
provider, search dialog, page actions (edit source, feedback, copy page,
Ask AI, open-in-chat, scroll top)
- `/api/search` → `createSearchRoute`, `/api/chat` → `createChatRoute`
(AI SDK v6; AI Gateway default, optional `GEISTDOCS_CHAT_PROXY_URL`)
- `llms.mdx` → `createDocsMarkdownRoute`, `sitemap.md` →
`createSitemapMarkdownRoute` (now includes an **Adapters** section)
- **New**: `/agents.md` via `createAgentsRoute`, backed by a new `agent`
readiness config
- `proxy.ts` → `createProxy` with explicit `markdownRoutes` for `/docs`
→ `llms.mdx` and `/adapters` → `adapters.mdx` (adds AI-agent UA
rewrites)
- CSS: `@vercel/geistdocs/styles.css` + slim local overrides (shadcn
tokens for remaining `components/ui`, body tint, prose inline code,
`#nd-*` tweaks); code blocks now use the geist Shiki theme
- Icons/logos from `@vercel/geistdocs/assets/*`; feedback via the
package action (same geistdocs.com endpoint + `siteId`)

### Kept local by design

- Curated `/llms.txt` index + `/llms-full.txt` corpus — the published
`AGENTS.md`/SKILL.md artifacts and integration tests reference this
exact contract
- The adapters section (README fetching, OG images, JSON-LD, feature
matrices, `adapters.mdx` markdown route) — now rendered inside the
package docs layout
- RSS and OG image routes (app-owned per the migration guide)
- Skipped `/.well-known/mcp.json`: no MCP servers configured, and the
proxy matcher must keep excluding `.well-known` for the served
agent-skills files

### Cleanup

- Deleted local copies: `components/geistdocs/*` chrome,
`components/ai-elements/*`, chat hooks/persistence, feedback server
actions, unused shadcn primitives, geistcn logo/icon fallbacks covered
by package assets
- Removed 13 now-unused deps (`ai@5`, `@ai-sdk/react@2`, `dexie`,
`jotai`, `cmdk`, `vaul`, `mermaid`, `nanoid`, `react-player`,
`use-stick-to-bottom`, `@orama/tokenizers`, `dexie-react-hooks`,
`next-themes`)
- Updated `docs-llms.test.ts` proxy assertions to the `createProxy`
markdown-route shape

### Behavior changes to be aware of

- Code blocks use the geist Shiki theme instead of GitHub light/dark
- Ask AI history is no longer persisted in IndexedDB (package owns the
panel)
- Adapters sidebar uses the standard geistdocs tree rendering instead of
the bespoke grouped sidebar
- Per-page markdown output appends the standard geistdocs footer links
(`/sitemap.md`, `/llms.txt`, `/agents.md`)

## Test plan

- `pnpm validate` green (knip + check + typecheck + test + build)
- Smoke-tested against `next build && next start`: `/`, `/docs`,
`/adapters`, `/agents.md`, `/llms.txt`, `/llms-full.txt`, `/sitemap.md`,
page-level `.md` URLs for both docs and adapters, `Accept:
text/markdown` negotiation, search API, JSON-LD, sr-only markdown hints,
edit-source URLs (`apps/docs/content/docs/{path}`), OSS navbar, page
actions
- Verified compiled CSS chunks contain the home grid, Shiki palette, and
geist utilities (note: stale turbopack dev caches from before this
change can serve incomplete CSS — `rm -rf apps/docs/.next` fixes it)

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [x] `pnpm validate` passes
- [x] Changeset added (or N/A — docs app + tests only, no package
behavior change)
- [x] Documentation updated (or N/A)

---------

Signed-off-by: molebox <rich@vercel.com>
2026-07-09 15:29:47 +02:00
josh b3123815fa docs: use xai/grok-4.5 across docs, guides, and examples (#687)
## summary

- swap chat model strings to `xai/grok-4.5` across the docs site, the
shipped guides, and the example bots, so the docs lead with the latest
model
- apps/docs: ai overview, ai-sdk-tools, and streaming pages, the two
landing-page code samples, and the live chat demo route
(`app/api/chat/route.ts`, previously `openai/gpt-4.1-mini`)
- packages/chat/resources/guides: the seven guides that use a chat model
(slack connect, slack + ai sdk, liveblocks, vercel blob, github code
review, daily digest, ai gateway)
- examples: nextjs-chat and nuxt-chat bots
- deliberately left non-chat model strings as they were, since grok-4.5
cannot fill those roles: the `openai/text-embedding-3-small` embedding
model and the `openai/gpt-4o-mini` reranker
- in the ai gateway guide, grok-4.5 is now the primary model but the
fallback list stays cross-provider (`anthropic/claude-opus-4.8`,
`google/gemini-3.1-pro-preview`) so the failover example still
demonstrates real cross-provider fallback
- also fixed a pre-existing prose/code mismatch in that guide, the
fallback prose said `claude-opus-4.7` while the code listed `4.8`, now
aligned to `4.8`

---------

Signed-off-by: dancer <josh@afterima.ge>
2026-07-09 13:35:36 +10:00
github-actions[bot] b1940d2374 chore(release): version packages (#660)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @chat-adapter/github@4.33.0

### Minor Changes

- 6750d59: Add Vercel Connect support to the GitHub adapter. A new
`installationToken` config option (string or resolver) supplies
installation access tokens directly, skipping the GitHub App private-key
JWT exchange, and an optional `webhookVerifier` verifies inbound
webhooks (e.g. Connect trigger-forwarded requests via a Vercel OIDC
token) in place of the GitHub webhook secret. Pair with
`connectGitHubAdapter()` from `@vercel/connect/chat`.

`botUserId` now also auto-detects from the `GITHUB_BOT_USER_ID` env var,
and the adapter learns its bot user id from the first comment it posts.
In Connect mode (where the bot user id can't be auto-detected from an
installation token) set `botUserId` / `GITHUB_BOT_USER_ID` to enable
self-message detection and avoid the adapter replying to its own
comments.

Note: the `connectGitHubAdapter()` helper ships in `@vercel/connect` —
release this adapter together with (or after) the `@vercel/connect`
version that adds the `@vercel/connect/chat` subpath so the documented
helper resolves.

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/linear@4.33.0

### Minor Changes

- 4115c94: Add Vercel Connect support to the Linear adapter. The
`accessToken` config option now accepts a resolver (`() => string |
Promise<string>`) in addition to a string, so tokens can be sourced from
Vercel Connect at runtime, and a new optional `webhookVerifier` verifies
inbound webhooks (e.g. Connect trigger-forwarded requests via a Vercel
OIDC token) in place of the Linear webhook secret. Pair with
`connectLinearAdapter()` from `@vercel/connect/chat`. Connect-mode
outbound calls outside webhook handling are supported via
`withInstallation(organizationId, fn)`.

Note: the `connectLinearAdapter()` helper ships in `@vercel/connect` —
release this adapter together with (or after) the `@vercel/connect`
version that adds the `@vercel/connect/chat` subpath so the documented
helper resolves.

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/shared@4.33.0

### Minor Changes

- d4c52ca: add `replaceBareMentions`, a context-aware bare-`@mention`
resolver that skips code spans, URLs, schemeless hosts, and existing
angle-bracket tokens before handing each real `@name` to a
platform-specific replacer

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [076fe5d]
  - chat@4.33.0
## @chat-adapter/x@4.33.0

### Minor Changes

- ef2542c: add X (Twitter) adapter: reply to public mentions, send and
receive direct messages, post and edit from the bot account, and like
posts, using the X API v2 with OAuth 2.0 and managed token refresh

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## chat@4.33.0

### Minor Changes

- ef2542c: add X (Twitter) adapter: reply to public mentions, send and
receive direct messages, post and edit from the bot account, and like
posts, using the X API v2 with OAuth 2.0 and managed token refresh

### Patch Changes

- 3abdc69: docs(adapters): add Cloudflare Agents as a vendor-official
state adapter (`agents/chat-sdk`) to the catalog and docs listing. It is
hidden from the create-chat-sdk CLI (Worker/Durable Objects runtime),
and the interactive state picker now filters out CLI-incompatible state
adapters.
- 0b63791: Raise the default message dedupe TTL from 5 to 10 minutes so
it outlives the longest platform redelivery window. Slack's Events API
retries up to ~5 minutes after the original delivery — exactly at the
old TTL boundary, where a retried event could miss the expired dedupe
entry from its first processing and be handled twice. Configurable
behavior is unchanged (`dedupeTtlMs` still overrides).
- 0c761f1: docs(adapters): add Dial as a vendor-official adapter
(`@getdial/chat-sdk-adapter`) to the catalog, docs listing, and CLI
scaffold spec
- 24a04d5: docs(adapters): add Photon as a vendor-official adapter
(`@photon-ai/chat-adapter-imessage`) to the catalog, docs listing, and
CLI scaffold spec
- 076fe5d: preserve skipped mention routing for debounce and message
patterns
## create-chat-sdk@0.2.0

### Minor Changes

- ba375ce: Add Vercel Connect support to the scaffolder. Pass
`--connect` (or choose **Vercel Connect** at the new interactive
auth-mode prompt) to authenticate the Slack, GitHub, and Linear adapters
with a Vercel Connect connector instead of stored provider secrets. The
generated `src/lib/bot.ts` spreads the matching helper from
`@vercel/connect/chat` into the adapter factory, `@vercel/connect` is
added to dependencies, and `.env.example` lists each connector UID (for
example `SLACK_CONNECTOR`) plus the recommended `GITHUB_BOT_USER_ID` for
GitHub, in place of native secrets.
- ef2542c: add X (Twitter) adapter: reply to public mentions, send and
receive direct messages, post and edit from the bot account, and like
posts, using the X API v2 with OAuth 2.0 and managed token refresh

### Patch Changes

- 3abdc69: docs(adapters): add Cloudflare Agents as a vendor-official
state adapter (`agents/chat-sdk`) to the catalog and docs listing. It is
hidden from the create-chat-sdk CLI (Worker/Durable Objects runtime),
and the interactive state picker now filters out CLI-incompatible state
adapters.
- 0c761f1: docs(adapters): add Dial as a vendor-official adapter
(`@getdial/chat-sdk-adapter`) to the catalog, docs listing, and CLI
scaffold spec
- 24a04d5: docs(adapters): add Photon as a vendor-official adapter
(`@photon-ai/chat-adapter-imessage`) to the catalog, docs listing, and
CLI scaffold spec
## @chat-adapter/tests@4.33.0

### Minor Changes

- e7a396a: Add two shared behavioral test contracts for adapter authors:

- `threadIdContract` — verifies an adapter's thread-id codec round-trips
(`decode(encode(x))`), prefixes ids with the adapter name, matches any
pinned encoded strings, and (optionally) distinguishes DM from non-DM
threads.
- `selfMessageContract` — verifies an adapter dispatches inbound
messages from other users (to `processMessage` by default) but ignores
messages the bot authored itself, so it never replies to itself.
Requires the matchers to be registered via `setupFiles:
["@chat-adapter/tests/setup"]`.

- a7fb1bc: Add `connectWebhookContract`, a shared Vitest suite for
verifying an adapter's Vercel Connect webhook verification. Given a
small per-adapter descriptor (how to build the adapter in Connect mode
and craft an inbound webhook), it asserts the behavior every
Connect-capable adapter shares: a `webhookVerifier` replaces the native
signature/secret check and gates inbound requests — accept (`200`) on a
truthy result, reject (`401`) on a thrown error or falsy result — and is
invoked with the request and raw body. Connect-capable adapters can opt
in with ~10 lines.
## @chat-adapter/discord@4.33.0

### Patch Changes

- d4c52ca: use the shared `replaceBareMentions` scanner for `@mention`
conversion so email addresses, `@handles` inside URLs, and mentions
inside code spans are no longer mangled into Discord mentions, and
already-formatted `<@id>` tokens are not double-wrapped
- 6de4572: Implement `rehydrateAttachment` on the Discord adapter.
Serialization strips an attachment's `fetchData` closure (queue/debounce
strategies), and consumers rebuild it via `adapter.rehydrateAttachment`.
The Discord adapter did not implement the method, so downstream
consumers could not download inbound Discord attachments after
deserialization. The Discord CDN `url` survives serialization, so
`fetchData` is now rebuilt to fetch that url (preserving its signed
query params), matching how the other adapters implement the method.
- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/gchat@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/messenger@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/slack@4.33.0

### Patch Changes

- 0b63791: Process Slack Socket Mode retry envelopes instead of
discarding them. Slack redelivers an event (immediately, +1 min, +5 min)
when a prior delivery wasn't acknowledged — including events sent while
the app had no open socket, e.g. during a restart or a routine
connection refresh. The adapter previously acked and dropped every
envelope with `retry_num > 0`, so such events were permanently lost even
though Slack redelivered them. Retries are now routed like first
deliveries (logged at info with `retry_num`/`retry_reason`);
`Chat.processMessage`'s message-id dedupe drops true duplicates.
- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/teams@4.33.0

### Patch Changes

- d4c52ca: use the shared `replaceBareMentions` scanner for `@mention`
conversion so email addresses, `@handles` inside URLs, and mentions
inside code spans are no longer mangled into `<at>` mention tags
- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/telegram@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/twilio@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/web@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/whatsapp@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [d4c52ca]
- Updated dependencies [076fe5d]
  - chat@4.33.0
  - @chat-adapter/shared@4.33.0
## @chat-adapter/state-ioredis@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [076fe5d]
  - chat@4.33.0
## @chat-adapter/state-memory@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [076fe5d]
  - chat@4.33.0
## @chat-adapter/state-pg@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [076fe5d]
  - chat@4.33.0
## @chat-adapter/state-redis@4.33.0

### Patch Changes

- Updated dependencies [3abdc69]
- Updated dependencies [0b63791]
- Updated dependencies [0c761f1]
- Updated dependencies [ef2542c]
- Updated dependencies [24a04d5]
- Updated dependencies [076fe5d]
  - chat@4.33.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-07 23:22:51 +01:00
josh ef2542c5fd feat(x): add X (Twitter) adapter (#682)
## summary

new `@chat-adapter/x` adapter for X (Twitter), built on the X API v2 and
the X Activity API. write bot logic once and reply to mentions, hold DM
conversations, post from the account, and like posts, like the other
Chat SDK adapters

what it supports:
- reply to public mentions (`post.mention.create`) and top-level posts
via `channel.post`
- send and receive direct messages (`dm.received` / `dm.sent`)
- edit and delete owned posts, delete own DM events
- likes as the only reaction (`emoji.heart` or `"like"`)
- buffered streaming: accumulates an LLM stream and posts once instead
of post+edit churn on a public timeline
- OAuth 2.0 user context with managed token refresh (rotating refresh
token persisted in the state adapter, optional AES-256-GCM encryption)
- webhook CRC and `x-twitter-webhooks-signature` verification

key design decisions:
- DMs are threaded by the other participant's user id (`x:dm:{userId}`)
because X DM webhooks carry no conversation id, only participants
- OAuth 2.0 only at runtime: DM send and read are verified to work on
OAuth 2.0 user tokens, so no OAuth 1.0a in the adapter (subscription and
webhook setup is one-time and handled in the X developer console)
- parsers were written against real captured payloads: mentions use the
v2 shape (author hydrated in `includes.users`), DMs use the legacy
Account Activity shape (`direct_message_events`,
`message_create.message_data`, a `users` map, and no conversation id)

also includes the `chat/adapters` catalog entry, docs page, CLI scaffold
spec, and `sample-messages.md` with real captured payloads

<details><summary>usage</summary>

```typescript
import { Chat } from "chat";
import { createXAdapter } from "@chat-adapter/x";

const bot = new Chat({
  userName: "mybot",
  adapters: { x: createXAdapter() },
});

bot.onNewMention(async (thread, message) => {
  await thread.post(`hi @${message.author.userName}!`);
});

bot.onDirectMessage(async (thread) => {
  await thread.post("hello from X");
});
```
</details>

## test plan

- adapter unit tests pass against the real captured payload shapes, with
regression tests for author-from-`includes` (mentions) and the legacy
`direct_message_events` shape (DMs)
- real captured `post.mention.create` and `dm.received` payloads
verified end-to-end through `handleWebhook`: signature verification,
routing, author resolution, and participant threading, plus
bad-signature rejection returns 401
- every write and read path fired live against the X API through the
adapter: top-level post, reply to a mention, like and unlike, edit,
delete, DM send, DM read, DM delete
- OAuth 2.0 managed token refresh exercised live (access and refresh
token rotation)

---------

Signed-off-by: dancer <josh@afterima.ge>
2026-07-07 23:07:36 +01:00
Mars Xiang 6de45723ef fix(discord): Implement rehydrateAttachment (#679)
## Summary

The Discord adapter never implemented `rehydrateAttachment`, so
consumers couldn't rebuild an attachment's `fetchData` after a message
was serialized and restored, and inbound Discord attachments couldn't be
downloaded once rehydrated. Every other URL/media adapter implements it.

Discord attachment URLs are directly fetchable and survive
serialization, so `fetchData` is rebuilt to fetch the URL (signed params
preserved, no auth header — the links are pre-signed), reading
`fetchMetadata?.url ?? attachment.url` like the other URL-based
adapters. Returns the attachment unchanged when there's no URL.

Adds unit tests and a changeset.

## Test plan

- `pnpm --filter @chat-adapter/discord test` — 248 passing
- `pnpm validate`

Signed-off-by: marsxiang5902 <marsxiang5902@gmail.com>
2026-07-07 23:03:55 +01:00
Ben Sabic e7a396ae70 feat(tests): add threadId + self-message contracts and adopt across adapters (#675)
## @chat-adapter/tests

Adds two shared behavioral contracts (self-tested against fakes,
exported, with a minor changeset):

- `threadIdContract` — verifies a thread-id codec round-trips
(`decode(encode(x))`), prefixes ids with the adapter name, matches
pinned encoded strings, and optionally distinguishes DM from non-DM.
- `selfMessageContract` — verifies an adapter dispatches inbound
messages from other users but ignores messages the bot authored itself
(uses the shared matchers).

## Adoption

- `threadIdContract` adopted across 10 adapters (github, linear, gchat,
teams, discord, slack, telegram, whatsapp, twilio, messenger), replacing
bespoke encode/decode/round-trip/isDM blocks while keeping error/edge
cases.
- `selfMessageContract` adopted where it cleanly applies (github,
messenger). Deliberately skipped where adapters dispatch-then-flag
`isMe` (linear, slack, gchat) or lack a network-free webhook self test
(teams, whatsapp, etc.).

Net ~−290 more lines. Stacked on #674.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-07 23:26:08 +10:00
Ben Sabic ac5a54ee1d test(adapters): adopt shared @chat-adapter/tests factories and matchers (#674)
Wires `@chat-adapter/tests` as a devDependency and registers its
matchers via `setupFiles: ["@chat-adapter/tests/setup"]` across all 11
platform adapters, then replaces bespoke local
`mockLogger`/`createMockState`/`createMockChatInstance` with the shared
factories and adopts `toHaveDispatched`/`not.toHaveDispatched` where
clean.

- 10 adapters migrated (gchat, messenger, teams, whatsapp, telegram,
discord, twilio, linear, github, slack). Positional
`createMockChatInstance(...)` call sites converted to the options API
(slack 100, linear 35).
- `web` left as-is — its suite uses the real `Chat`/`createMemoryState`
for e2e, so the shared factories don't apply.
- Platform SDK mocks (Octokit, WebClient, socket-mode, `@linear/sdk`,
`fetch`) and the Phase 1 `connectWebhookContract` descriptors are left
intact.

Net ~−540 lines of duplicated test scaffolding. Stacked on #673.
Tests-only, no changeset.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-07 23:20:32 +10:00
Ben Sabic 840c0d16e9 test(adapters): migrate Vercel Connect webhook tests to connectWebhookContract (#673)
Adopts the shared `connectWebhookContract` from `@chat-adapter/tests` in
the Slack, GitHub, and Linear suites, replacing the bespoke
`webhookVerifier` blocks (verifier pass → 200, throw/falsy → 401,
invoked with request + raw body, precedence over a native secret).
Adapter-specific Connect tests are kept (token resolvers, GitHub bot-id
capture, type-level mutual exclusivity, 400-on-invalid-JSON, Linear
identity/`withInstallation`).

Each descriptor keeps `initialize()` network-free (GitHub `botUserId`,
Slack `_botUserId` to skip `auth.test`, Linear stubs
`resolveConnectIdentity`). Twilio is intentionally not included — it has
a single generic `webhookVerifier` usage with no 200/401 gating suite to
migrate.

First of three stacked test-generalization PRs. Tests-only, no
changeset.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-07 23:14:05 +10:00
zaadevofc eb466e526f docs: add chat-adapter-zaileys community adapter (#677)
## Summary

Adds **chat-adapter-zaileys** to the community adapters catalog — a
WhatsApp adapter powered by
[Zaileys](https://github.com/zeative/zaileys), a batteries-included
TypeScript wrapper around the unofficial WhatsApp Web API.

- npm: https://www.npmjs.com/package/chat-adapter-zaileys
- Repo: https://github.com/zeative/chat-adapter-zaileys
- Docs: https://zeative.github.io/chat-adapter-zaileys/

## What it adds vs the existing Baileys community adapter

- Real `thread.fetchMessages` history backed by a pluggable message
store (memory/SQLite/Postgres/Redis/Convex), with cursor pagination and
`rehydrateAttachment` for queue/debounce strategies
- Cards render as **native WhatsApp buttons** with `chat.onAction`
round-trips
- Poll votes decrypted natively — no `messageSecret` bookkeeping, works
across restarts
- `scheduleMessage` support (persisted scheduler)
- Opt-in slash-command routing to `chat.onSlashCommand`
- QR/pairing auth, reconnection, and session persistence handled by the
underlying client

## Files changed (per `.agents/skills/add-adapter`)

- `apps/docs/content/adapters/community/zaileys.mdx` — docs page with
feature matrix
- `apps/docs/content/adapters/community/meta.json` — slug added to
Platforms
- `apps/docs/adapters.json` — registry entry
- `packages/integration-tests/src/documentation-test-utils.ts` —
`chat-adapter-zaileys` + `zaileys` in `VALID_DOC_PACKAGES`

## Validation

- `pnpm --filter chat build` ✓
- `pnpm --filter @chat-adapter/integration-tests test` → 914/914 ✓
- `pnpm --filter chat typecheck` ✓
- `pnpm check` + `pnpm konsistent` ✓

Signed-off-by: zeative <zaadevofc@gmail.com>
2026-07-06 09:27:43 +10:00
OmriBenShoham 0c761f1bdd docs(adapters): add Dial as vendor-official adapter (#676)
Adds Dial as a vendor-official adapter — SMS, MMS, iMessage, and inbound
voice-call transcripts for Chat SDK.

- `vendor-official/dial.mdx` adapter page (following the Photon / Linq /
Sendblue format)
- catalog entry in `packages/chat/src/adapters/index.ts` with
`DIAL_API_KEY` / `DIAL_FROM_NUMBER_ID` / `DIAL_WEBHOOK_SECRET`
- `create-chat-sdk` scaffold spec entry
- registry entry in `adapters.json` + `dial` added to vendor-official
`meta.json`
- integration-test doc lists + changeset

Repo: https://github.com/GetDial-AI/chat-sdk-adapter · npm:
`@getdial/chat-sdk-adapter` · Dial docs:
https://docs.getdial.ai/integrations/agent-clients/vercel-chat-sdk

The adapter maps a phone conversation to a Chat SDK thread (identified
by the pair of phone numbers — Dial-owned and peer), an SMS/MMS/iMessage
to a message with optional media attachments, and a completed voice
call's transcript to a message on the caller's thread. Outbound sends
and transcript fetches go through the official `@getdial/sdk`; inbound
webhooks are HMAC-SHA256 verified against a per-subscription signing
secret with constant-time compare.

### Validation
- `pnpm --filter chat build` — clean
- `pnpm --filter chat typecheck` — clean
- `pnpm --filter create-chat-sdk typecheck` — clean
- `pnpm --filter @chat-adapter/integration-tests exec vitest run
src/docs-adapters.test.ts` — 361/361 passed
- `pnpm check` (ultracite) — clean
- `pnpm konsistent` — 34 files, no violations
2026-07-06 09:21:34 +10:00
Ben Sabic d4c52cade3 refactor(shared): share the bare-mention scanner across Discord, Teams, and Slack (#652)
The Discord adapter (fixed in #651) converts bare `@mentions` with a
regex. A single-character lookbehind can't tell whether an `@` sits
inside a URL, a code span, or an email host, so it still mangles cases
the regex can't see. The Slack adapter already had a robust
character-scanning resolver — `replaceBareMentions` — that handles
exactly those cases, but it lived inside `adapter-slack`.

This lifts that scanner into `@chat-adapter/shared` and points every
adapter that does bare-mention conversion at it: Slack (dedup, no
behavior change), Discord, and Teams — which had the identical
`/@(\w+)/g` → `<at>$1</at>` bug in two places.

## What changed

- **`@chat-adapter/shared`** — new `replaceBareMentions` (+
`MentionReplacer` type), moved verbatim from Slack. It skips
inline/fenced code, scheme + schemeless URLs, and existing `<…>` tokens
before handing each real `@name` to a platform-specific replacer. Adds a
dedicated test file (the scanner had no direct tests before).
- **Slack** — sources `replaceBareMentions` from `@chat-adapter/shared`;
local `mentions.ts` deleted. Behavior unchanged.
- **Discord** — regex → scanner in both conversion sites.
- **Teams** — same fix for `<at>…</at>` mention tags.

## What this fixes (Discord + Teams)

Across both the `{markdown}`/AST and `{raw}`/plain-string paths:

| input | before | after |
|---|---|---|
| `https://github.com/@vercel` | `https://github.com/<@vercel>` |
preserved |
| `twitter.com/@jack` | `twitter.com/<@jack>` | preserved |
| `` `ping @here` `` | `` `ping <@here>` `` | preserved |
| `<@123>` (Discord, raw) | `<<@123>>` | preserved |

Emails (`user@example.com`), period-prefixed mentions
(`docs.@everyone`), and existing tokens keep working.

## Notes

- `.changeset/config.json` uses `fixed: [["chat", "@chat-adapter/*"]]`,
so the `@chat-adapter/shared` **minor** bump carries the whole family to
a minor release; the `discord` / `teams` `patch` changesets exist for
their changelog text. Slack has no behavioral change, so it gets no
changeset.
- Rebased on top of #651. That PR's `discord-email-mentions.md`
changeset stays; this PR's scanner supersedes its regex implementation,
so the two Discord changelog entries read as a progression.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-02 18:45:16 +01:00
Thomas Dietert 0b63791b66 fix(slack): process Socket Mode retry envelopes instead of dropping them (#667)
Fixes #666

## Summary

Both `slack_event` handlers (`startSocketMode` and
`runSocketModeListener`) ack and discard every envelope with `retry_num
> 0`. Slack retries an event (immediately, +1 min, +5 min) when a prior
delivery wasn't acked — including events that arrived while the app had
**no open socket** (restart, deploy, or Slack's routine connection
refreshes). For those, the retry is the only delivery the app ever sees,
so dropping it permanently loses the event (production incident details
in #666).

- **`@chat-adapter/slack`**: route retry envelopes through
`routeSocketEvent` like first deliveries (it acks per envelope type,
preserving the 3s ack window), and log them at info with `retry_num` /
`retry_reason` so redelivery is observable. Duplicate protection is
unchanged and sufficient: `Chat.processMessage` dedupes on `message.id`
(the Slack event `ts`, identical on a retry) via `state.setIfNotExists`.
- **`chat`**: raise the default `DEDUPE_TTL_MS` from 5 to 10 minutes.
Slack's final retry fires ~5 minutes after the original delivery —
exactly at the old TTL boundary, where the dedupe entry from the first
processing could expire just before the retry arrives and cause a
double-process. `dedupeTtlMs` config still overrides.

Behavior note for review: apps that relied on retries being invisible
will now see redelivered events flow through — deduped when already
handled, processed when not. That is the intended semantic:
at-least-once delivery from Slack, exactly-once handling via the SDK's
dedupe.

## Test plan

- Replaced the `"skips retries"` test with `"processes retries like
first deliveries (dedupe drops true duplicates)"` — asserts a
`retry_num: 1` envelope is acked and reaches `processMessage`.
- Updated the default-TTL test to 10 minutes; the custom-`dedupeTtlMs`
test is unchanged.
- `pnpm validate` passes end to end (knip, check, typecheck, test,
build); `pnpm --filter chat --filter @chat-adapter/slack test` = 1028 +
506 passing.


Signed-off-by: tdietert <thomasd@mercury.com>
2026-07-02 17:26:27 +01:00
Ben Sabic 3abdc69103 docs(adapters): add Cloudflare Agents as vendor-official state adapter (#669)
Adds Cloudflare Agents as a vendor-official **state** adapter —
`agents/chat-sdk`'s `createChatSdkState()`, a Chat SDK `StateAdapter`
that stores subscriptions, locks, queues, dedupe keys, thread/channel
state, transcripts, and history in Durable Object SQLite via
`ChatSdkStateAgent` sub-agents.

- `vendor-official/cloudflare-agents.mdx` state-adapter page (Agent
setup, wrangler DO migration, sharding, config, storage/cleanup)
- catalog entry in `packages/chat/src/adapters/index.ts` (`group:
vendor-official`, `type: state`)
- registry entry in `adapters.json` + `cloudflare-agents` in
vendor-official `meta.json`
- integration-test doc lists + changeset

Repo: https://github.com/cloudflare/agents · package `agents`
(`agents/chat-sdk`) ·
[docs](https://developers.cloudflare.com/agents/runtime/communication/chat-sdk/)

### Not wired into the create-chat-sdk CLI
This adapter runs inside a Cloudflare Worker with Durable Objects, not
the generated Next.js runtime, so it is intentionally kept out of the
scaffold:
- added to `CLI_INCOMPATIBLE_ADAPTERS` (rejected via `--adapter`, hidden
from the platform picker and e2e run, like `lark`/`matrix`)
- new `listCliStateAdapters()` filters the interactive **state** picker
and the `--help` adapter list (the state picker previously used raw
`listStateAdapters()` and would have offered it, then thrown on
selection)

### Tests
- `catalog/display.test.ts` — `listCliStateAdapters`: returns only state
adapters, includes `memory`/`redis`, and excludes `cloudflare-agents`
while asserting it *is* in the raw catalog
- `catalog/selection.test.ts` —
`resolveAdapterValue("cloudflare-agents")` throws "not supported"
- `cli/program.test.ts` — `buildAdapterList()` help text omits
`cloudflare-agents`
- existing `CLI_SCAFFOLD_SPEC covers every catalog adapter` +
docs-adapters/docs-content suites cover the catalog entry, registry
parity, and MDX imports

### Validation
- create-chat-sdk: **178 passed**, typecheck clean
- integration docs suites pass; Biome + knip clean

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-03 01:55:05 +10:00
Ben Sabic 24a04d5653 docs(adapters): add Photon as vendor-official adapter (#668)
Adds Photon as a vendor-official adapter — iMessage for Chat SDK.

- `vendor-official/photon.mdx` adapter page (following the Linq /
Sendblue / Kapso format)
- catalog entry in `packages/chat/src/adapters/index.ts` with
cloud/self-host credential modes
- `create-chat-sdk` scaffold spec entry
- registry entry in `adapters.json` + `photon` added to vendor-official
`meta.json`
- integration-test doc lists + changeset

Repo: https://github.com/photon-hq/vercel-chat-adapter-imessage · npm:
`@photon-ai/chat-adapter-imessage` · built on
[spectrum-ts](https://github.com/photon-hq/spectrum-ts)

The adapter runs in three modes — **Cloud** ([Spectrum
Cloud](https://app.photon.codes)), **self-hosted** (gRPC), and **local**
(on-device, macOS) — auto-detected from environment variables. Cloud
mode delivers inbound messages via HMAC-signed webhooks; DMs can be
replied to cold from a webhook delivery.

### Notes
- Catalog slug is `photon`; docs code examples use `imessage` as the
adapter key to match the upstream README.
- Feature flags encode the README's remote-only caveats (reactions /
editing / typing / modals as `partial`, mentions as DMs-only; no
history, thread info, or reaction removal).

### Validation
- `docs-adapters` integration tests — 1237 passed (catalog↔registry
parity, peerDeps↔PackageInstall alignment)
- `create-chat-sdk` e2e scaffold — 175 passed (scaffolds every catalog
adapter, incl. photon)
- `chat` + `create-chat-sdk` typecheck, Biome check, and konsistent —
clean

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-07-03 01:40:06 +10:00