929 Commits

Author SHA1 Message Date
Ben Sabic 6adca3617e feat(slack): support egress proxies (#916)
Adds proxy configuration for Slack connections that previously bypassed
`webClientOptions.agent`. Socket Mode now uses that agent for HTTP and
WebSocket connections, and new `fetch` and `fileTransport` options cover
response URLs, webhook forwarding, and attachment downloads.

```ts
createSlackAdapter({
  webClientOptions: { agent },
  fetch: proxyFetch,
  fileTransport,
});
```

Includes a setup example and guidance on the destination restrictions
custom download transports need to enforce.

Closes #596

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-09 17:46:53 +00:00
Ben Sabic f893470e19 fix(telegram): preserve links during truncation (#915)
Stops Telegram MarkdownV2 messages from being cut at a backtick inside a
link destination. Rendered text that fits the length limit now ships
exactly as it was rendered. The safe-boundary trimmer only runs on the
over-limit slice, where it removes an incomplete link, code span, or
underline before the ellipsis.

This covers message posts, edits, and attachment captions. The original
underscore URL case was already fixed; this closes the remaining
backtick and truncation cases.

Changes:

- Return text that fits the length limit unchanged instead of trimming
it
- Replace the per-marker scans with a single pass that groups delimiter
positions by marker and counts closed links
- Treat two backticks as a cut fence only at the end of the slice, so
empty inline code spans keep their trailing content
- Count a bare `]` as a link closer unless it ends the slice
- Pair `__` underline separately from `_` italic
- Test the trimmer directly, covering escaped `*`, `_`, `~`, empty code
spans, bare brackets, and underline
- Import the production helpers in the adapter tests instead of local
copies
- Update the changeset and docs paragraph

Closes #866

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-09 18:39:41 +01:00
Ben Sabic 2e2426d17e feat(teams): add installation lifecycle events (#914)
## summary

- add `onInstalled` and `onUninstalled` handlers for Teams personal,
group chat, and team installations, including upgrades that add or
remove the bot from the app manifest
- expose a persistable `event.channelId` for proactive messages through
`bot.channel(channelId).post()`, alongside raw platform metadata for
application-owned persistence
- route outbound operations through the service URL encoded in each
thread, preserving the explicit `apiUrl` override
- document stable bot, tenant, and team installation keys separately
from selected-channel destinations, and clean up for both `remove` and
`remove-upgrade`
- exercise the documented persistence example with regressions for
upgrade cleanup, team-scoped removal, reinstall replacement, tenant
isolation, and personal/group conversations

closes #847

---------

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-09-09 18:30:10 +01:00
Ben Sabic ea025af7ac feat(postgres): allow migration-managed schemas (#913)
Adds `autoCreateSchema: false` so applications can manage PostgreSQL
tables and indexes through migrations and run the bot with restricted
database permissions.

```ts
createPostgresState({
  url: process.env.POSTGRES_URL,
  autoCreateSchema: false,
});
```

Automatic schema creation remains enabled by default. The docs include
the migration SQL and required grants. The expired-cache behavior
reported in the issue is already fixed in the base branch.

Closes #722

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-09 17:18:11 +00:00
Max 056d8830b2 feat(history): support uncapped per-user retention (#904)
## Summary

Allow `history.user.maxPerUser: false` and legacy
`transcripts.maxPerUser: false` to disable count-based eviction. Apps
that retain complete user history can keep entries beyond the default
200. Numeric limits still trim older entries, and retention TTL remains
independent.

## Test plan

Added coverage for retaining 205 entries with no cap, the default
200-entry cap, and legacy configuration merging. The existing
numeric-limit test remains. `pnpm validate` passed.

## 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-09-06 15:27:47 +10:00
github-actions[bot] 85a37c896f chore(release): version packages (#878)
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/gchat@4.40.0

### Minor Changes

- f485255: Harden webhook tenant isolation, require explicit Google Chat
bot identity for reliable mention handling, use native Google Chat
pagination, isolate Slack caches, and bound recording storage.

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/slack@4.40.0

### Minor Changes

- 51322dd: Decode the bot's own mention in incoming Slack messages. The
adapter now resolves `<@U_BOT>` to the bot's display name
(`@<DisplayName>`) the same way it resolves every other user mention,
instead of leaving the raw user-ID markup in place, and sets `isMention`
on the parsed message by detecting the bot's ID in the raw event text.
This keeps `message.text` self-describing for downstream consumers (LLM
prompts, classifiers) while preserving mention detection, which
previously depended on the raw ID markup surviving in the text.
- d4a1f03: Rotate long-running native Slack streams before Slack expires
them. Once a stream segment passes `streamSegmentMaxAgeMs` (default four
minutes) the adapter finalizes it at the next paragraph break and
continues the reply in a new message, closing and reopening code fences,
repeating table headers, replaying open task cards and the plan title,
and keeping the agent session in `processing`. A segment Slack already
expired during an idle gap is recovered the same way instead of failing
the reply.
- 2cc8cc3: Surface custom status text in the Agent messaging experience.
`startTyping` and `setAssistantStatus` with a custom status now call the
legacy `assistant.threads.setStatus` endpoint, whose compatibility
bridge renders the text in the agent-session loading UX — instead of
silently dropping the text and showing the generic "Working…" indicator.
Clearing (empty status) still transitions the session to `active` via
the Agent Sessions lifecycle.

### Patch Changes

- 78021c0: pass workspace context to suggested prompt resolvers in Agent
view
- 8b6d7f3: Reactivate Slack Agent Sessions when `startTyping` receives
an empty status.
- c2b6bff: Use the Slack bot user ID for bot-authored messages when a
bot profile is available.
- f485255: Harden webhook tenant isolation, require explicit Google Chat
bot identity for reliable mention handling, use native Google Chat
pagination, isolate Slack caches, and bound recording storage.
- 8fdaf4a: Stream post-and-edit fallback updates through Slack's
`markdown_text` field instead of `text`, so live-updating messages
render markdown while the stream is in progress (and get the
12,000-character ceiling rather than 4,000)
- 7609d8f: Validate external request targets before sending credentials,
message content, or attachment requests.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/teams@4.40.0

### Minor Changes

- aaeede7: emit Teams bot-join events and expose the bot user ID
- 4a0b5c0: Add `tooltip` to `Button` and `LinkButton`, and a `width`
hint to `Card`. The Teams adapter and the `@chat-adapter/teams/cards`
subpath render them as the Adaptive Card action `tooltip` and the
`msteams` full-width card property; other adapters ignore them. Emitted
Adaptive Cards now declare schema version 1.5, which is the version that
introduced action tooltips. Buttons with a `callbackUrl` keep their
`tooltip` and other fields when the URL is swapped for a callback token.

### Patch Changes

- a8de95b: Fall back to Teams activity group metadata when the explicit
conversation type is missing.
- f485255: Harden webhook tenant isolation, require explicit Google Chat
bot identity for reliable mention handling, use native Google Chat
pagination, isolate Slack caches, and bound recording storage.
- 7062c39: preserve outgoing @names as plain text instead of generating
mention markup without matching entities
- 7609d8f: Validate external request targets before sending credentials,
message content, or attachment requests.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/telegram@4.40.0

### Minor Changes

- 043386b: Add Telegram Business mode support. Opt in via `businessMode:
true`.
  
The adapter handles `business_connection`, `business_message`, and
`edited_business_message` updates, encodes business threads as
`telegram:biz:{connectionId}:{chatId}`, and passes
`business_connection_id` on outbound sends, edits, typing, file uploads,
and inline-keyboard callbacks. Business threads are their own channel,
slash commands route through `onSlashCommand`, deletes use
`deleteBusinessMessages`, and connection state is cached in the state
adapter so a revoked connection is honoured by every instance. Reactions
on business threads throw a `NotImplementedError`, since the Bot API has
no business variant of `setMessageReaction`.

### Patch Changes

- 43dba3d: Skip unused plain-text conversion for rich and Markdown
native draft updates. Plain-text fallback and final delivery keep their
existing behavior.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/whatsapp@4.40.0

### Minor Changes

- 31bce0a: export `WhatsAppApiError` for non-2xx Graph API responses. It
carries Meta's numeric `errorCode`, `providerMessage`, `type`,
`details`, `subcode`, `traceId`, the HTTP `status`, and the `raw`
envelope, and maps `code` onto the shared `AdapterError` taxonomy
(`RATE_LIMITED`, `AUTH_FAILED`, `PERMISSION_DENIED`, `NOT_FOUND`).
Transport failures and unparseable response bodies now throw
`NetworkError` instead of leaking raw fetch errors, and error messages
carry Meta's message or a bounded excerpt instead of the full response
body.

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## chat@4.40.0

### Minor Changes

- 4a0b5c0: Add `tooltip` to `Button` and `LinkButton`, and a `width`
hint to `Card`. The Teams adapter and the `@chat-adapter/teams/cards`
subpath render them as the Adaptive Card action `tooltip` and the
`msteams` full-width card property; other adapters ignore them. Emitted
Adaptive Cards now declare schema version 1.5, which is the version that
introduced action tooltips. Buttons with a `callbackUrl` keep their
`tooltip` and other fields when the URL is swapped for a callback token.

### Patch Changes

- f485255: Harden webhook tenant isolation, require explicit Google Chat
bot identity for reliable mention handling, use native Google Chat
pagination, isolate Slack caches, and bound recording storage.
- b7c9316: Tighten AI tool and queued-message scoping, isolate
direct-message conversations, preserve ephemeral follow-ups, consume
callback tokens once, and mark external link metadata as untrusted.
## @chat-adapter/discord@4.40.0

### Patch Changes

- b7c9316: Tighten AI tool and queued-message scoping, isolate
direct-message conversations, preserve ephemeral follow-ups, consume
callback tokens once, and mark external link metadata as untrusted.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/github@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/instagram@4.40.0

### Patch Changes

- 7609d8f: Validate external request targets before sending credentials,
message content, or attachment requests.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/linear@4.40.0

### Patch Changes

- 3d2cb22: Keep Linear agent-session events on one stable thread and
handle sessions created by automation or without a root comment.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/messenger@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/notion@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/shared@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
## @chat-adapter/twilio@4.40.0

### Patch Changes

- b7c9316: Tighten AI tool and queued-message scoping, isolate
direct-message conversations, preserve ephemeral follow-ups, consume
callback tokens once, and mark external link metadata as untrusted.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/web@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/x@4.40.0

### Patch Changes

- 7609d8f: Validate external request targets before sending credentials,
message content, or attachment requests.
- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
  - @chat-adapter/shared@4.40.0
## @chat-adapter/state-ioredis@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
## @chat-adapter/state-memory@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
## @chat-adapter/state-pg@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
## @chat-adapter/state-redis@4.40.0

### Patch Changes

- Updated dependencies [f485255]
- Updated dependencies [b7c9316]
- Updated dependencies [4a0b5c0]
  - chat@4.40.0
## @chat-adapter/tests@4.40.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@chat-adapter/teams@4.40.0 chat@4.40.0 @chat-adapter/discord@4.40.0 @chat-adapter/gchat@4.40.0 @chat-adapter/x@4.40.0 @chat-adapter/github@4.40.0 @chat-adapter/instagram@4.40.0 @chat-adapter/whatsapp@4.40.0 @chat-adapter/linear@4.40.0 @chat-adapter/messenger@4.40.0 @chat-adapter/web@4.40.0 @chat-adapter/notion@4.40.0 @chat-adapter/shared@4.40.0 @chat-adapter/twilio@4.40.0 @chat-adapter/slack@4.40.0 @chat-adapter/state-ioredis@4.40.0 @chat-adapter/tests@4.40.0 @chat-adapter/state-memory@4.40.0 @chat-adapter/state-pg@4.40.0 @chat-adapter/telegram@4.40.0 @chat-adapter/state-redis@4.40.0
2026-09-05 06:06:47 +00:00
‌ 31bce0a7a0 feat(whatsapp): expose typed API errors (#896)
- export `WhatsAppApiError` so consumers can handle Meta error codes
without parsing error messages
- expose HTTP status, provider details, optional subcode and trace ID,
and the raw response
- cover message requests, media uploads, and media metadata failures
while preserving existing error messages and `AdapterError`
compatibility
- add regression coverage and document error handling

closes #712

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-05 05:53:12 +00:00
‌ 7062c395d0 fix(teams): preserve outgoing mention text (#898)
- keep outgoing `@names` as plain text instead of generating `<at>`
markup without the mention entities Teams requires
- preserve multi-word names across plain text, raw, markdown, and AST
messages
- keep incoming mention decoding and explicit raw markup unchanged
- add formatter and send/edit regression tests and document that
plain-text names do not notify users

closes #853
2026-09-05 15:23:02 +10:00
‌ aaeede70be feat(teams): dispatch bot join events (#899)
- dispatch `onMemberJoinedChannel` when the bot joins a Teams channel or
group chat
- expose `botUserId` from the configured app identity
- preserve channel routing, inviter identity, and webhook `waitUntil`
tracking
- add regression tests and document the bot-only scope

addresses the bot-join portion of #847; personal install/uninstall hooks
remain separate

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-05 05:21:54 +00:00
Max 43dba3de1a perf(telegram): skip unused plain draft rendering (#900)
## Summary

Native Telegram drafts rendered the accumulated Markdown as plain text
before replacing it with the rich or Markdown result. This computes only
the selected draft format, while keeping the rich-to-Markdown-to-plain
fallback and final delivery behavior unchanged.

For an 82-chunk rich response, the pinned reproduction reduces discarded
plain conversion from 83 calls over 139,400 characters to 0. It sends
the same 83 Bot API requests with identical bodies and outcomes.

Copy and run:

```sh
git clone --depth 1 --filter=blob:none --sparse --branch repro/telegram-lazy-plain https://github.com/onmax/repros.git telegram-lazy-plain-repro
cd telegram-lazy-plain-repro
git sparse-checkout set telegram-lazy-plain telegram-lazy-plain-fix
cd telegram-lazy-plain
corepack pnpm install --frozen-lockfile --ignore-scripts && corepack pnpm verify
cd ../telegram-lazy-plain-fix
corepack pnpm install --frozen-lockfile --ignore-scripts && corepack pnpm verify
```

## Test plan

- `pnpm --filter @chat-adapter/telegram test`, 335 tests
- `pnpm --filter @chat-adapter/telegram typecheck`
- `pnpm --filter @chat-adapter/telegram build`
- `pnpm validate`
- Eight reproduction controls against the source build

## 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 N/A, public behavior and configuration are unchanged

Model: GPT-5.6 Sol. Harness: Codex.

Signed-off-by: Max <maximogarciamtnez@gmail.com>
2026-09-05 15:00:32 +10:00
Camden Amata 8fdaf4a9d7 fix(slack): render markdown in post-and-edit stream fallback (#901)
## Summary

`SlackAdapter.stream()`'s post-and-edit fallback passes its accumulated
text to `postMessage`/`editMessage` as a **bare string**, which
`SlackFormatConverter.toSlackPayload` resolves to Slack's `text` field:

```ts
if (fallback.message) {
  await this.editMessage(threadId, fallback.message.id, committable);
} else {
  fallback.message = await this.postMessage(threadId, committable);
}
```

`text` renders classic mrkdwn (`*bold*`) only, not the GFM the renderer
emits — so **every intermediate edit during a fallback stream shows the
user raw `**`/`#`/backtick syntax**. Only a caller-side final
replacement recovers the formatting, and only for the last frame; every
frame before it was wrong while it was on screen.

It's also a materially smaller ceiling: `text` on `chat.update` caps at
4,000 characters vs. `markdown_text`'s 12,000, so fallback mode fails on
long answers sooner than it needs to — `msg_too_long` mid-stream,
independent of the formatting bug.

`StreamingMarkdownRenderer.getCommittableText()` is already documented
as text "safe for append-only streaming" — it holds back unclosed inline
markers (`**`, `*`, `~~`, `` ` ``, `[`) and unconfirmed table headers.
And the renderer's own class doc is explicit about which side owns
conversion: "Outputs markdown (not platform text). Format conversion
still happens in the adapter's editMessage → renderPostable → fromAst
pipeline." Native mode honors that; fallback mode ships the same output
through the wrong field. Wrapping it as `{ markdown: committable }`
routes it into the same `markdown_text` field native mode streams into,
via `toSlackPayload`'s existing `markdown` branch — nothing new to
build.

Per Slack's reference for
[`chat.postMessage`](https://docs.slack.dev/reference/methods/chat.postMessage/)
and
[`chat.update`](https://docs.slack.dev/reference/methods/chat.update/),
`markdown_text` needs no scope beyond the `chat:write` the adapter
already holds and carries no app-feature gate. Its one constraint is
mutual exclusivity with `text`/`blocks` (`markdown_text_conflict`), and
`toSlackPayload` emits exactly one field per branch, so that conflict
can't arise here.

One behavior change worth naming: Slack documents that mobile
notifications use `message.text` for block-based messages, and doesn't
document how push previews are derived for `markdown_text`. In practice
we see no notification regression — our app already posts
`markdown_text` in native mode and for final message replacements — but
flagging it rather than leaving it to be discovered.

### How we hit this

A Slack Workflow Builder–authored message (posted as a bot, no real
`event.user`) leaves `recipient_user_id` invalid for native streaming,
so the adapter drops into fallback from the first send and stays there
for the whole answer. That root cause is separate and app-side — not
part of this PR — but it's what made this reproducible for us. Note it
isn't the only route in: once `switchToFallback()` latches
`nativeStreamingBroken` on a `feature_not_enabled` / `method_deprecated`
/ `unknown_method` error, *every* subsequent stream on that adapter
instance takes this path.

## Test plan

- `pnpm validate` — 43/43 tasks pass (knip, lint, typecheck, test,
build).
- `pnpm test:workspace` — 3,714 passed / 6 skipped, 102 files.
- New test in `packages/adapter-slack/src/index.test.ts`: `streams
fallback updates through markdown, not plain text` — asserts every
`postMessage`/`editMessage` call in fallback mode receives `{ markdown:
<string> }` rather than a bare string, and that markdown syntax survives
to the last frame.
- Three existing `native streaming fallback` tests read the posted
payload to assert content; updated to read `.markdown` via a small
`markdownOf` helper. These are the only places in the suite that assumed
the bare-string shape — worth knowing for anyone auditing the blast
radius.

## 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 (patch, `@chat-adapter/slack`)
- [x] Documentation updated (N/A — no public API change)

Signed-off-by: CamdenA21 <camden@sandstone.com>
2026-09-05 13:52:59 +10:00
Hiroki Osame 32a2717e68 ci: fix release pipeline for changesets action v2 (#902)
## Problem

The Release workflow has failed on every run since Sep 3, so no package
version has been published since then. Every changeset merged to `main`
is stranded waiting for the version PR to regenerate.

The cause is a dependency mismatch introduced by a Dependabot bump. #880
updated the [Changesets GitHub
Action](https://github.com/changesets/action) from v1.9.0 to v2.1.1, but
did not touch `@changesets/cli`, which stayed on v2. The action's v2
release requires Changesets CLI v3 and rejects CLI v2 on every run, so
the workflow fails before it can version or publish anything. The
mismatch went unnoticed because Dependabot manages GitHub Actions and
npm dependencies separately.

## Changes

- Bump `@changesets/cli` from ^2.29.8 to ^3.0.1 to match what the action
requires. The existing `.changeset/config.json` is fully compatible with
CLI v3; nothing else in the release flow changes.
- Update the Release workflow to the action's renamed inputs: `publish`
→ `publish-script`, `commit` → `commit-message`, `title` → `pr-title`.
The v2 action hard-fails on the old names, and it removed `commitMode:
github-api` entirely (GitHub API pushes are now the default behavior, so
removing it preserves what the workflow did before).

Once merged, the Release workflow should go green and update the open
version PR (#878) to include all changesets accumulated since Sep 3,
unblocking the next publish.

Signed-off-by: Hiroki Osame <hiroki.osame@gmail.com>
2026-09-05 12:59:31 +10:00
Hiroki Osame 2cc8cc3f80 fix(slack): surface custom status text in the Agent messaging experience (#897)
## summary

- restore custom loading labels for `startTyping` and
`setAssistantStatus` under `agentView`, which stopped displaying after
#862 moved status updates to the native sessions API
- send custom labels through `assistant.threads.setStatus` with
`loading_messages`; `setAssistantStatus` preserves explicit arrays, then
configured defaults, then falls back to the custom status
- keep native `processing` and initiator attribution when
`startTyping()` has no custom status, and use native `active` when
clearing
- add regression coverage for message precedence, native routing,
clearing, and native API failures
- verify custom labels in DMs and channels, streamed completion, and
real native stop-button cancellation against locally built packages

## limitations

custom labels and native session state are not equivalent: in the test
workspace, the custom-label path displayed the requested text but did
not create a native processing session or stop button, even with
`agent_session_stopped` enabled

use `startTyping()` without custom text when native processing and stop
behavior are required; an existing native processing indicator can also
take precedence over a custom label

Slack's [native sessions API](https://docs.slack.dev/ai/agent-sessions/)
does not accept custom loading text, so this restores labels through the
[legacy status
endpoint](https://docs.slack.dev/reference/methods/assistant.threads.setStatus/)
without promising identical lifecycle behavior

---------

Co-authored-by: dancer <josh@afterima.ge>
2026-09-04 21:40:02 +00:00
Mohammed Mansoor Ahmed 4a0b5c0c3f feat(cards): add button tooltips and a card width hint (#895)
Buttons can now show hover text, and a card can ask to be rendered wider
than usual. Both are small hints: Teams renders them, and every other
adapter leaves the card exactly as it was before.

### Button tooltips

`Button` and `LinkButton` take an optional `tooltip`. On Teams it
appears when someone hovers over the button.

```tsx
<Card title="Deploy request">
  <Actions>
    <Button id="approve" style="primary" tooltip="Ships this build to production">
      Approve
    </Button>
    <LinkButton url="https://example.com/build/1234" tooltip="Opens the build log in your browser">
      View build
    </LinkButton>
  </Actions>
</Card>
```

The same option is available on the plain builder functions:

```ts
Button({ id: "approve", label: "Approve", tooltip: "Ships this build to production" })
```

Tooltips survive the `callbackUrl` flow too. When a button's callback
URL is swapped for a token before the card is sent, every other field on
the button is kept, so a tooltip on a callback button shows up just like
one on a regular button.

### Full-width cards

`Card` takes an optional `width`, either `"default"` or `"full"`. Teams
draws a `"full"` card wider than its usual size, which suits tables and
digests. It does not stretch the card across the whole chat pane, that
is how Teams defines full width.

```tsx
<Card title="Weekly digest" width="full">
  <Table headers={["Service", "Uptime"]} rows={[["api", "99.98%"], ["web", "99.95%"]]} />
</Card>
```

### What Teams receives

- `tooltip` becomes the `tooltip` on the Adaptive Card action, for both
submit and open-URL buttons.
- `width="full"` becomes `msteams: { width: "full" }` on the card.
- The card now declares Adaptive Card version 1.5, which is the version
that introduced action tooltips. Teams accepts cards up to 1.6 for bots,
so nothing changes for existing cards beyond the version number.
- The runtime-free `@chat-adapter/teams/cards` helpers understand both
new fields as well, so apps that build Teams cards without the full
adapter get the same result.

### Why only Teams

Slack and Google Chat have no hover text for buttons. They do have
screen-reader labels, but those replace the button text for assistive
technology rather than adding to it, so mapping a tooltip onto them
would change what a screen reader announces. The fields are documented
as Teams-only for that reason.

### Small cleanup along the way

The JSX runtime used to decide whether a set of props belonged to a
`Card` by checking for a fixed list of prop names. Any new `Card` prop
that was not on that list was silently dropped. Since `Card` is the only
component left once every other one has been matched, the props are now
used directly and the list is gone.

Docs for both props are on the cards page and in the API reference.

---------

Signed-off-by: Mohammed Mansoor Ahmed <mansoorahmed.mohammed@gmail.com>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-04 22:08:15 +10:00
psychomet 043386b52c feat(telegram): add Business mode support (#888)
- Adds Telegram Business mode support to `@chat-adapter/telegram`
- Handles `business_connection`, `business_message`, and
`edited_business_message` updates
- Passes `business_connection_id` on outbound sends, edits, typing, and
file uploads
- Opt-in via `businessMode: true` (default off, backward compatible)

Closes #887

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-04 04:03:01 +00:00
dcbuilder.eth d4a1f03afc fix(slack): rotate long native streams before expiry (#884)
Slack expires native streams after roughly five minutes. Finalize
long-running streams after four minutes by default and continue in a
fresh segment so late appends do not fail with
message_not_in_streaming_state. Preserve open fenced code blocks by
closing and reopening them across the segment boundary. The threshold is
configurable with streamSegmentMaxAgeMs.

---------

Signed-off-by: dcbuild3r <dcbuilder@pm.me>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-09-04 10:54:00 +10:00
Hiroki Osame 51322dde8f fix(slack): decode the bot self mention in incoming message text (#891)
## Problem

When a Slack message mentions the bot, the adapter resolves every
mention in the text to a display name — except the bot's own. `<@U_BOT>`
stays in `message.text` as raw user-ID markup while other mentions
become `@<DisplayName>`.

Downstream consumers therefore see inconsistent text. For LLM-based
consumers this is actively harmful: small models classify differently
depending on whether the mention arrived as self-describing
`@<DisplayName>` text or as cryptic `<@U_BOT>` markup, and there is no
way for a consumer to tell a real mention from a lookalike string
without re-implementing Slack's mrkdwn rules.

The reason the bot's own mention was left raw is detection coupling:
`Chat.detectMention` matched `@botUserId` / `<@botUserId>` in the text,
so resolving the markup would have hidden the mention from detection.

## Changes

- `resolveInlineMentions` now decodes the bot's own mention like any
other: `<@U_BOT>` resolves to `@<DisplayName>` (via the same
`users.info` lookup and cache as all other mentions).
- Because resolution renders the ID markup away, detection moves to
where the ID is still known: `parseSlackMessage` tests the raw event
text for the bot's mention (labeled, unlabeled, and bare `@ID` forms)
and sets `isMention` on the parsed message. `Chat.detectMention` remains
as the fallback for username-style mentions on the rendered text.
- The `skipSelfMention` option is removed; the history/thread fetch
paths that passed `skipSelfMention: false` now behave identically to
live events, which also fixes an inconsistency where edited messages
(`parseSlackMessageSync`) never resolved mentions at all.

## Behavior change

`message.text` for a message that mentions the bot changes from
`<@U_BOT> hello` to `@Vercel Bot hello` (the bot's resolved display
name). Mention detection is preserved: `isMention` is set from the raw
event text, and the labeled form is matched by the new detection
patterns. Apps matching the raw `<@U_BOT>` markup in `message.text`
should match the resolved display name or read `message.raw` instead.

## Relation to #355

#355 intentionally introduced the self-mention skip: in multi-workspace
installs, the request-scoped bot ID was being resolved before mention
detection ran, which broke `onNewMention` for those workspaces. This PR
preserves that guarantee without the markup coupling — the
multi-workspace replay test from that scenario is updated and still
asserts that a plain `message` event containing the bot's mention sets
`isMention: true` and routes to `onNewMention`. Detection additionally
covers the labeled form (`<@U_BOT|Name>`) that resolution now produces.

---------

Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-09-03 18:42:04 +01:00
Ben Sabic f485255bcf fix(adapters): harden webhook tenant isolation (#877)
Multi-workspace Slack now ignores commands and interactions when their
installation cannot be found, and channel names stay isolated per
workspace.

Google Chat no longer learns its identity from incoming mentions, and
forward history reads use bounded native pagination.

Webhook logs avoid message content. The example app protects preview
routing, records only successful verified deliveries, and caps recording
size and retention.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-09-03 17:30:32 +01:00
Ben Sabic b7c9316bfd fix(chat): tighten conversation boundaries (#875)
Agent tools now keep user profile lookups behind approval and apply
conversation scope to typing indicators.

Discord thread targets are checked against their parent channel, and
private slash-command follow-ups stay private. Twilio direct messages no
longer share history or scope across recipients.

Queued messages restore their own conversation context before handlers
run. Callback tokens are bound to their action and conversation, expire
sooner, and can only be used once.

Link preview metadata is clearly marked as untrusted and bounded before
it reaches an AI prompt.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-09-03 17:10:58 +01:00
Max a8de95bcc4 fix(teams): infer missing conversation types (#879)
## Summary

Teams can omit `conversationType` while still sending
`conversation.isGroup`. Use `isGroup` and team context as a fallback so
`a:`-prefixed group chats are not treated as DMs. An explicit
`conversationType` still takes precedence.

## Test plan

- `pnpm validate`
- `pnpm --filter @chat-adapter/teams test`, 276 tests passed

## Checklist

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

Signed-off-by: onmax <maximogarciamtnez@gmail.com>
2026-09-04 00:25:29 +10:00
dependabot[bot] aa3e014824 build(deps): bump pnpm/action-setup from 6.0.9 to 6.0.10 (#881)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from
6.0.9 to 6.0.10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pnpm/action-setup/releases">pnpm/action-setup's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.10</h2>
<h2>What's Changed</h2>
<ul>
<li>docs(README): point users to the successor pnpm/setup action by <a
href="https://github.com/BlackHole1"><code>@​BlackHole1</code></a> in <a
href="https://redirect.github.com/pnpm/action-setup/pull/282">pnpm/action-setup#282</a></li>
<li>refactor: introduce restore keys for cache by <a
href="https://github.com/SukkaW"><code>@​SukkaW</code></a> in <a
href="https://redirect.github.com/pnpm/action-setup/pull/280">pnpm/action-setup#280</a></li>
<li>ci: use pnpm 11 for <code>pr-check</code> by <a
href="https://github.com/jamietanna"><code>@​jamietanna</code></a> in <a
href="https://redirect.github.com/pnpm/action-setup/pull/284">pnpm/action-setup#284</a></li>
<li>fix: update pnpm to v11.19.0 by <a
href="https://github.com/jamietanna"><code>@​jamietanna</code></a> in <a
href="https://redirect.github.com/pnpm/action-setup/pull/283">pnpm/action-setup#283</a></li>
<li>docs: update README to include devEngines.packageManager by <a
href="https://github.com/nemchik"><code>@​nemchik</code></a> in <a
href="https://redirect.github.com/pnpm/action-setup/pull/273">pnpm/action-setup#273</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/jamietanna"><code>@​jamietanna</code></a> made
their first contribution in <a
href="https://redirect.github.com/pnpm/action-setup/pull/284">pnpm/action-setup#284</a></li>
<li><a href="https://github.com/nemchik"><code>@​nemchik</code></a> made
their first contribution in <a
href="https://redirect.github.com/pnpm/action-setup/pull/273">pnpm/action-setup#273</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pnpm/action-setup/compare/v6...v6.0.10">https://github.com/pnpm/action-setup/compare/v6...v6.0.10</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pnpm/action-setup/commit/0977fd99725f1db4007ccb2928dbb4e90d06cc86"><code>0977fd9</code></a>
docs: Update README to include devEngines.packageManager (<a
href="https://redirect.github.com/pnpm/action-setup/issues/273">#273</a>)</li>
<li><a
href="https://github.com/pnpm/action-setup/commit/48261aca053e825d84804e8ce05524d558249ac9"><code>48261ac</code></a>
fix: update pnpm to v11.19.0 (<a
href="https://redirect.github.com/pnpm/action-setup/issues/283">#283</a>)</li>
<li><a
href="https://github.com/pnpm/action-setup/commit/75677f717d48404e86ae8ee4891543f40de175aa"><code>75677f7</code></a>
ci: use pnpm 11 for <code>pr-check</code> (<a
href="https://redirect.github.com/pnpm/action-setup/issues/284">#284</a>)</li>
<li><a
href="https://github.com/pnpm/action-setup/commit/769ae71fb33e6e448a5dc92ad5da997c268eecec"><code>769ae71</code></a>
refactor: introduce restore keys for cache (<a
href="https://redirect.github.com/pnpm/action-setup/issues/280">#280</a>)</li>
<li><a
href="https://github.com/pnpm/action-setup/commit/6fed91f804570c1144bfe1911c348642cb986bd4"><code>6fed91f</code></a>
docs(README): point users to the successor pnpm/setup action (<a
href="https://redirect.github.com/pnpm/action-setup/issues/282">#282</a>)</li>
<li>See full diff in <a
href="https://github.com/pnpm/action-setup/compare/0ebf47130e4866e96fce0953f49152a61190b271...0977fd99725f1db4007ccb2928dbb4e90d06cc86">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pnpm/action-setup&package-manager=github_actions&previous-version=6.0.9&new-version=6.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)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 00:23:37 +10:00
dependabot[bot] 84a023b01a build(deps): bump changesets/action from 1.9.0 to 2.1.1 (#880)
Bumps [changesets/action](https://github.com/changesets/action) from
1.9.0 to 2.1.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/changesets/action/releases">changesets/action's
releases</a>.</em></p>
<blockquote>
<h2>v2.1.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/changesets/action/pull/721">#721</a>
<a
href="https://github.com/changesets/action/commit/da1ea291ef93203a08fad360eac36f4858dd0514"><code>da1ea29</code></a>
Thanks <a
href="https://github.com/KEBABSELLER6"><code>@​KEBABSELLER6</code></a>!
- Fixed typo in renamed inputs from v1 to v2</li>
</ul>
<h2>v2.1.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/changesets/action/pull/718">#718</a>
<a
href="https://github.com/changesets/action/commit/3b7c71c440e75fca5d335f779290425fc1117fe8"><code>3b7c71c</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Add a <code>cwd</code> input to the root action,
<code>/select-mode</code>, <code>/version</code>, <code>/pack</code>,
and <code>/publish</code> sub-actions to set the current working
directory to execute Changesets in. This input existed in v1 but was
incorrectly removed.</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/changesets/action/pull/716">#716</a>
<a
href="https://github.com/changesets/action/commit/6f58ba35801ec075a1c87569277591f8ef1a3029"><code>6f58ba3</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Update <code>pr-status</code> message to link to the new faq page</li>
</ul>
<h2>v2.0.0</h2>
<h3>Major Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/692">#692</a>
<a
href="https://github.com/changesets/action/commit/cb3f0110d7423cd340b1c5d63584c0ea6ee63959"><code>cb3f011</code></a>
Thanks <a
href="https://github.com/Andarist"><code>@​Andarist</code></a>! -
Release commits and tags are now pushed using the GitHub API by
default.</p>
<p>Replace the <code>commit-mode</code> input with the boolean
<code>push-with-git-cli</code> input. Set <code>push-with-git-cli:
true</code> to continue using the Git CLI.</p>
<p>Regardless of the push mode, custom GitHub tokens must be passed
explicitly through the <code>github-token</code> input. The
<code>GITHUB_TOKEN</code> environment variable and credentials
configured by <code>actions/checkout</code> or embedded in remote URLs
are not substitutes for this input. When the Git CLI is enabled,
<code>github-token</code> takes precedence over those repository
credentials.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/680">#680</a>
<a
href="https://github.com/changesets/action/commit/ca57073900dc678254406a052a03c0c8824e319d"><code>ca57073</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Add a new <code>push-git-tags</code> option that complements
<code>create-github-releases</code> to control specifically if git tags
should be created but not GitHub releases.</p>
<p>If <code>create-github-releases</code> was previously set to
<code>false</code>, which also indirectly disabled git tag creation, git
tags will now be created instead by default. If this is not desired, set
<code>push-git-tags</code> to <code>false</code> explicitly.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/657">#657</a>
<a
href="https://github.com/changesets/action/commit/4f718b5921490b1efeb015a81abb6a4633892851"><code>4f718b5</code></a>
Thanks <a
href="https://github.com/Andarist"><code>@​Andarist</code></a>! -
Removed compatibility support for old Changesets v1.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/681">#681</a>
<a
href="https://github.com/changesets/action/commit/73591071e61c7f61cf322dd7a6341dc29a8e1d4e"><code>7359107</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Rename the root action inputs and outputs to better match the
sub-actions' conventions.</p>
<p>Inputs:</p>
<ul>
<li><code>version</code> -&gt; <code>version-script</code></li>
<li><code>publish</code> -&gt; <code>publish-script</code></li>
<li><code>commit</code> -&gt; <code>commit-message</code></li>
<li><code>title</code> -&gt; <code>pr-title</code></li>
<li><code>branch</code> -&gt; <code>pr-base-branch</code></li>
</ul>
<p>Outputs:</p>
<ul>
<li><code>pull-request-number</code> -&gt; <code>pr-number</code></li>
</ul>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/674">#674</a>
<a
href="https://github.com/changesets/action/commit/164652bdd60525670d95291addb8c6f92833ac60"><code>164652b</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Remove support for passing custom GitHub token through the GITHUB_TOKEN
environment variable. It should be passed to the
<code>github-token</code> input instead.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/659">#659</a>
<a
href="https://github.com/changesets/action/commit/5649ff41b8eb1b7a281011e45df781c6d4628fd0"><code>5649ff4</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Remove <code>cwd</code> option for <code>changesets/action</code>. Use
the step <code>working-directory</code> option instead to change the
directory.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/673">#673</a>
<a
href="https://github.com/changesets/action/commit/823cf741ca57c5e22652123d3a847dfafcd77ca0"><code>823cf74</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Update to Changesets v3 packages</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/695">#695</a>
<a
href="https://github.com/changesets/action/commit/469993ce5783c6a38e72541d4ba0d25588702b9a"><code>469993c</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Removed <code>.npmrc</code> handling when the <code>NPM_TOKEN</code>
environment variable is set.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/changesets/action/blob/main/CHANGELOG.md">changesets/action's
changelog</a>.</em></p>
<blockquote>
<h1><code>@​changesets/action</code></h1>
<h2>2.1.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/changesets/action/pull/721">#721</a>
<a
href="https://github.com/changesets/action/commit/da1ea291ef93203a08fad360eac36f4858dd0514"><code>da1ea29</code></a>
Thanks <a
href="https://github.com/KEBABSELLER6"><code>@​KEBABSELLER6</code></a>!
- Fixed typo in renamed inputs from v1 to v2</li>
</ul>
<h2>2.1.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/changesets/action/pull/718">#718</a>
<a
href="https://github.com/changesets/action/commit/3b7c71c440e75fca5d335f779290425fc1117fe8"><code>3b7c71c</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Add a <code>cwd</code> input to the root action,
<code>/select-mode</code>, <code>/version</code>, <code>/pack</code>,
and <code>/publish</code> sub-actions to set the current working
directory to execute Changesets in. This input existed in v1 but was
incorrectly removed.</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/changesets/action/pull/716">#716</a>
<a
href="https://github.com/changesets/action/commit/6f58ba35801ec075a1c87569277591f8ef1a3029"><code>6f58ba3</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Update <code>pr-status</code> message to link to the new faq page</li>
</ul>
<h2>2.0.0</h2>
<h3>Major Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/692">#692</a>
<a
href="https://github.com/changesets/action/commit/cb3f0110d7423cd340b1c5d63584c0ea6ee63959"><code>cb3f011</code></a>
Thanks <a
href="https://github.com/Andarist"><code>@​Andarist</code></a>! -
Release commits and tags are now pushed using the GitHub API by
default.</p>
<p>Replace the <code>commit-mode</code> input with the boolean
<code>push-with-git-cli</code> input. Set <code>push-with-git-cli:
true</code> to continue using the Git CLI.</p>
<p>Regardless of the push mode, custom GitHub tokens must be passed
explicitly through the <code>github-token</code> input. The
<code>GITHUB_TOKEN</code> environment variable and credentials
configured by <code>actions/checkout</code> or embedded in remote URLs
are not substitutes for this input. When the Git CLI is enabled,
<code>github-token</code> takes precedence over those repository
credentials.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/680">#680</a>
<a
href="https://github.com/changesets/action/commit/ca57073900dc678254406a052a03c0c8824e319d"><code>ca57073</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Add a new <code>push-git-tags</code> option that complements
<code>create-github-releases</code> to control specifically if git tags
should be created but not GitHub releases.</p>
<p>If <code>create-github-releases</code> was previously set to
<code>false</code>, which also indirectly disabled git tag creation, git
tags will now be created instead by default. If this is not desired, set
<code>push-git-tags</code> to <code>false</code> explicitly.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/657">#657</a>
<a
href="https://github.com/changesets/action/commit/4f718b5921490b1efeb015a81abb6a4633892851"><code>4f718b5</code></a>
Thanks <a
href="https://github.com/Andarist"><code>@​Andarist</code></a>! -
Removed compatibility support for old Changesets v1.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/681">#681</a>
<a
href="https://github.com/changesets/action/commit/73591071e61c7f61cf322dd7a6341dc29a8e1d4e"><code>7359107</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Rename the root action inputs and outputs to better match the
sub-actions' conventions.</p>
<p>Inputs:</p>
<ul>
<li><code>version</code> -&gt; <code>version-script</code></li>
<li><code>publish</code> -&gt; <code>publish-script</code></li>
<li><code>commit</code> -&gt; <code>commit-message</code></li>
<li><code>title</code> -&gt; <code>pr-title</code></li>
<li><code>branch</code> -&gt; <code>pr-base-branch</code></li>
</ul>
<p>Outputs:</p>
<ul>
<li><code>pull-request-number</code> -&gt; <code>pr-number</code></li>
</ul>
</li>
<li>
<p><a
href="https://redirect.github.com/changesets/action/pull/674">#674</a>
<a
href="https://github.com/changesets/action/commit/164652bdd60525670d95291addb8c6f92833ac60"><code>164652b</code></a>
Thanks <a href="https://github.com/bluwy"><code>@​bluwy</code></a>! -
Remove support for passing custom GitHub token through the GITHUB_TOKEN
environment variable. It should be passed to the
<code>github-token</code> input instead.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/changesets/action/commit/8488615a623b1b9c987934bb89eae8af6a946ac1"><code>8488615</code></a>
v2.1.1</li>
<li><a
href="https://github.com/changesets/action/commit/d7669c894a691cf0908002c33cdd57df54a1ef3b"><code>d7669c8</code></a>
Version Packages (<a
href="https://redirect.github.com/changesets/action/issues/722">#722</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/da1ea291ef93203a08fad360eac36f4858dd0514"><code>da1ea29</code></a>
Fix typo in renamed inputs (<a
href="https://redirect.github.com/changesets/action/issues/721">#721</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/7545547fe488a27b3f26e807596ed000538f7ba7"><code>7545547</code></a>
Version Packages (<a
href="https://redirect.github.com/changesets/action/issues/719">#719</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/3043070b0dd173ef012baa5fcd205d38c7dadc72"><code>3043070</code></a>
Prevent test warning annotations (<a
href="https://redirect.github.com/changesets/action/issues/720">#720</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/3b7c71c440e75fca5d335f779290425fc1117fe8"><code>3b7c71c</code></a>
Add back cwd input (<a
href="https://redirect.github.com/changesets/action/issues/718">#718</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/6f58ba35801ec075a1c87569277591f8ef1a3029"><code>6f58ba3</code></a>
Update pr-status message links to new faq (<a
href="https://redirect.github.com/changesets/action/issues/716">#716</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/e52ce9e59b5ed97f1b897b0ce507e6283737c7e4"><code>e52ce9e</code></a>
Version Packages (<a
href="https://redirect.github.com/changesets/action/issues/715">#715</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/78fdc6bb2e67f6fb5b4a083b64971e9a46a7749d"><code>78fdc6b</code></a>
Exit pre mode (<a
href="https://redirect.github.com/changesets/action/issues/714">#714</a>)</li>
<li><a
href="https://github.com/changesets/action/commit/187a104ea060c8ea199979460bf91705980c64da"><code>187a104</code></a>
Update deps for stable (<a
href="https://redirect.github.com/changesets/action/issues/709">#709</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/changesets/action/compare/a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d...8488615a623b1b9c987934bb89eae8af6a946ac1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=changesets/action&package-manager=github_actions&previous-version=1.9.0&new-version=2.1.1)](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)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 00:23:20 +10:00
‌ 78021c09d0 fix(slack): pass team id to agent view prompt resolvers (#889)
## summary

- pass the workspace `team_id` from `app_home_opened` events to dynamic
`suggestedPrompts` resolvers
- prefer `authorizations[0].team_id` with the top-level `team_id` as a
fallback
- add regression coverage for multi-workspace agent view apps
- fixes #874

## test plan

- reproduce the missing workspace context with Slack's documented event
envelope
- verify the resolver receives the workspace id
- `pnpm build`
- `pnpm --filter @chat-adapter/slack test`
- `pnpm --filter @chat-adapter/integration-tests test`
- `pnpm typecheck`
- `pnpm exec ultracite check packages/adapter-slack/src/index.ts
packages/adapter-slack/src/index.test.ts
packages/adapter-slack/src/types.ts`

Signed-off-by: dancer <josh@afterima.ge>
2026-09-04 00:15:11 +10:00
Ward Peeters 8b6d7f3ab3 fix(slack): clear Agent Session typing status (#882)
## Summary

- map non-empty `startTyping` statuses to the Slack Agent Session
`processing` state
- map an empty `startTyping` status to `active`, allowing generic Chat
SDK callers to end typing without posting a message
- preserve the legacy `assistant.threads.setStatus` path and
`initiator_user_id` propagation

This fixes the regression reported in mastra-ai/mastra#22670. Mastra
already clears typing through the generic `startTyping(threadId, "")`
contract, so the platform-specific lifecycle translation belongs in the
Slack adapter.
2026-09-03 14:04:11 +00:00
dcbuilder.eth 3d2cb22a41 fix(linear): stabilize agent session event threads (#885)
## Summary

Route every event in a Linear agent session to the stable issue/session
thread instead of deriving a new thread from each source comment. Also
accept sessions created without a root comment and attribute
creator-less sessions to a distinct Linear automation identity instead
of the bot itself, which previously caused automation-created sessions
to be dropped as self-messages.

## Test plan

- bunx vitest run packages/adapter-linear/src/index.test.ts (164 passed)
- bunx tsc -p packages/adapter-linear/tsconfig.json --noEmit
- bunx biome check on changed files

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (git commit -s)
- [ ] pnpm validate passes (targeted package validation run)
- [x] Changeset added
- [x] Documentation updated (N/A; documented stable session thread
format is now used consistently)

---------

Signed-off-by: dcbuild3r <dcbuilder@pm.me>
Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-09-02 18:48:35 +01:00
dcbuilder.eth c2b6bff07c fix(slack): use bot user ID for message authors (#883)
## Summary

Slack bot messages can include both an app-scoped bot_id and a
user-scoped bot_profile.user_id. Use the user ID for normalized message
authors when available so identity, self-message checks, and downstream
user lookups operate on the same ID Slack uses for users.

## Test plan

- bunx vitest run packages/adapter-slack/src/index.test.ts
packages/adapter-slack/src/markdown.test.ts (490 passed)
- bunx tsc -p packages/adapter-slack/tsconfig.json --noEmit
- bunx biome check on changed files

---------

Signed-off-by: dcbuild3r <dcbuilder@pm.me>
Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: dancer <josh@afterima.ge>
2026-09-02 16:52:17 +00:00
Ben Sabic 7609d8f60e fix(adapters): validate external request targets (#876)
Adapters now reject untrusted destinations before sending credentials,
message content, or attachment requests.

Teams Connector and Graph calls stay on known Microsoft hosts, Instagram
downloads stay on trusted Meta hosts, and Slack response URLs are
checked before use.

XChat now handles CRC challenges itself and rejects tokens that could be
reused to forge webhook signatures.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-31 17:13:03 +00:00
Noppakorn Kaewsalabnil f691ad5848 docs: add LINE community adapter (#873)
## Summary

- Add `chat-adapter-line` to the community adapter catalog.
- Add a hand-authored LINE adapter docs page with configuration,
webhook, messaging, and feature-matrix details.
- Register `chat-adapter-line` as a valid docs code-example import.

## Validation

- `pnpm --filter @chat-adapter/integration-tests test --
src/docs-adapters.test.ts --coverage=false` — 467 tests passed
- `pnpm --filter @chat-adapter/integration-tests test --
src/docs-content.test.ts --coverage=false` — 91 tests passed
- `pnpm --filter @chat-adapter/integration-tests test --
src/docs-llms.test.ts --coverage=false` — 145 tests passed
- `pnpm exec biome check apps/docs/content/adapters/community/line.mdx
apps/docs/content/adapters/community/meta.json apps/docs/adapters.json
packages/integration-tests/src/documentation-test-utils.ts` — passed

Signed-off-by: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com>
2026-08-30 18:42:17 +10:00
Ben Sabic 894fc7c7b3 docs: redirect conversation-history and update Vercel Connect page (#870)
Redirects /docs/conversation-history to /docs/history, since the History
guide already covers the old transcripts content. Removes the Vercel
Connect beta callout and adds related links to the Chat SDK docs and The
Complete Guide to Vercel Connect.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 10:31:31 -07:00
github-actions[bot] 7a1798bdfd chore(release): version packages (#841)
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/shared@4.39.0

### Minor Changes

- 153bd96: guard Messenger attachment downloads against SSRF and
oversized responses

`downloadAttachment` in `@chat-adapter/shared` accepts an optional
`hosts` allowlist that restricts downloads, including redirect targets,
to the given hosts and their subdomains. The Messenger adapter uses it
to download attachment media only from Meta's `fbsbx.com` and
`fbcdn.net` hosts, with the shared SSRF guard, 25 MB size cap, and 30
second timeout. External fallback and link-share URLs are rejected
before any network request.

- bb92688: secure anonymous attachment downloads against SSRF and
oversized responses

`@chat-adapter/shared` gains `downloadAttachment`, a guarded downloader
that refuses private and internal addresses (as URL literals, through
DNS resolution, and after redirects), decodes compressed responses, caps
the body size at 25 MB, and bounds the whole download with a 30 second
timeout. All of these are configurable, including the transport for
proxied deployments.

The Teams adapter uses it for anonymous attachment downloads. HTTPS
attachments on any public host keep working, plain-HTTP URLs are
refused, and the Bot Framework Emulator's loopback connector now uses
bot authentication so local development keeps working.

- e71bfea: Add `normalizeCodeFences` to `@chat-adapter/shared`: a
code-fence normalizer for platforms whose triple-backtick fences treat
the text after the opening fence as code rather than a CommonMark info
string. Only paired fences become code blocks (unpaired fences, fences
inside inline code, and fences on quoted lines stay literal text), text
following a closing fence cannot be promoted to a block construct, and
per-segment callbacks keep text-level rewrites out of code content.

The WhatsApp adapter now uses it when parsing incoming messages: the
first line of a code block is preserved in message text and formatted
content, and bold/strikethrough rewriting no longer corrupts fenced
code.

- b6fa24c: guard attachment downloads across the remaining adapters

Slack, Discord, and WhatsApp attachment downloads now go through the
shared guarded downloader: private and internal addresses are refused
(as URL literals, through DNS resolution, and after redirects),
responses are capped at 25 MB, and downloads time out after 30 seconds.
Slack sends the bot token only on hops to trusted Slack origins, and
WhatsApp keeps its access token on Meta's media hosts and the configured
Graph origin. Telegram enforces the same size cap and timeout with the
Web Fetch API so downloads keep working in runtimes like Cloudflare
Workers.

`downloadAttachment` in `@chat-adapter/shared` now resolves `headers`
per hop (pass a function to control what each redirect target receives),
forwards the resolved headers to custom transports, and accepts an
`onResponse` hook to reject unexpected final responses before the body
is read.

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [5b538f6]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
  - chat@4.39.0
## @chat-adapter/slack@4.39.0

### Minor Changes

- 2ce2be0: Add Slack Agent Sessions lifecycle support, native stop
cancellation through `thread.signal`, automatic session titles, and
session stop/title-change events while preserving the legacy
`assistant_view` compatibility path.

### Patch Changes

- 864d922: Keep alert attachment content on normalized Slack messages.
Attachments that aren't link unfurls now contribute their pretext, title
(linked to `title_link` when present, with the URL also surfaced in
`message.links`), text, and fields instead of being dropped; `fallback`
fills in when nothing else on the attachment carries content. Matching
how Slack renders these fields, they are treated as plain text unless
listed in the attachment's `mrkdwn_in` array, so literal `*`, `_`, and
backticks in alert text survive normalization. Tables inside attachment
blocks now stay adjacent to their attachment's text.

Because attachment content is part of `message.text`, mention detection
and `onMessage` pattern handlers see it too: an attachment that quotes
the bot's mention routes to `onNewMention`, and patterns match alert
text. Handlers that should ignore other integrations' alerts can check
`message.author.isBot`.

- 7c26965: prevent attachment downloads from sending credentials to
untrusted hosts
- e71bfea: Preserve the first line of incoming Slack code blocks when
extracting message text and formatted content.

Only paired triple-backtick fences become code blocks: an unpaired
fence, a fence inside inline code or a `<…>` token, and a fence on a
quoted line all stay literal text, matching how Slack renders them. Bold
and strikethrough rewriting no longer touches fenced code content, and
text following a closing fence can no longer turn into a blockquote,
heading, or list.

- b6fa24c: guard attachment downloads across the remaining adapters

Slack, Discord, and WhatsApp attachment downloads now go through the
shared guarded downloader: private and internal addresses are refused
(as URL literals, through DNS resolution, and after redirects),
responses are capped at 25 MB, and downloads time out after 30 seconds.
Slack sends the bot token only on hops to trusted Slack origins, and
WhatsApp keeps its access token on Meta's media hosts and the configured
Graph origin. Telegram enforces the same size cap and timeout with the
Web Fetch API so downloads keep working in runtimes like Cloudflare
Workers.

`downloadAttachment` in `@chat-adapter/shared` now resolves `headers`
per hop (pass a function to control what each redirect target receives),
forwards the resolved headers to custom transports, and accepts an
`onResponse` hook to reject unexpected final responses before the body
is read.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/telegram@4.39.0

### Minor Changes

- c4a359e: require webhook verification by default with an explicit
unverified opt-in
- 26a06ca: Add `mentionOnReply`: when enabled, a reply to one of the
bot's own messages reports `isMention`, so a bot in a group keeps the
conversation going without the handle being repeated. Off by default, so
existing mention-only bots are unaffected, and readable from
`TELEGRAM_MENTION_ON_REPLY`. Implicit forum-topic replies and the bot's
own echoed messages never count, and polling mode now retries the
startup `getMe` lazily so a transient outage cannot leave mention
detection disabled.
- d5ebec1: Implement `reply` in the Telegram adapter so `Thread.reply()`
threads the answer to its target instead of throwing
`NotImplementedError`. The reference travels as Bot API
`reply_parameters` and covers text, rich messages, documents,
attachments and media groups; `allow_sending_without_reply` keeps
delivery working when the target has been deleted. Malformed reply
target ids are rejected before anything is sent, and a rich-message
gateway that rejects `reply_parameters` falls back to a regular threaded
send.
- a18e792: Describe the message kinds Telegram sends with no text and no
file. A shared location, venue, contact, poll, dice, game, invoice or
story used to arrive as an empty message: the payload carried the
content, but a handler reading `text` saw nothing. Each now gets a short
literal description (`📍 55.75, 37.61`, `👤 Ada Lovelace +1555…`, `📊 Lunch
or dinner?`), and the structured payload stays on the raw message for
anyone who needs the numbers.
- a0ba986: Parse stickers and animations. A sticker used to arrive as an
empty message, since it carries no text, and an animation (Telegram GIF)
was dropped entirely. A sticker now reports the emoji it stands for as
its text (falling back to the sticker set name, then to "sticker") plus
an attachment matching its real format: an image for a still WebP
sticker, a video for a WebM one, a file for a Lottie (TGS) one. An
animation arrives as a single video attachment; the redundant `document`
field Telegram sets alongside it for backward compatibility is no longer
reported as a second attachment.

### Patch Changes

- eddcd7e: Return Telegram file downloads as portable ArrayBuffer data
while preserving Buffer support in the shared attachment contract.
- b6fa24c: guard attachment downloads across the remaining adapters

Slack, Discord, and WhatsApp attachment downloads now go through the
shared guarded downloader: private and internal addresses are refused
(as URL literals, through DNS resolution, and after redirects),
responses are capped at 25 MB, and downloads time out after 30 seconds.
Slack sends the bot token only on hops to trusted Slack origins, and
WhatsApp keeps its access token on Meta's media hosts and the configured
Graph origin. Telegram enforces the same size cap and timeout with the
Web Fetch API so downloads keep working in runtimes like Cloudflare
Workers.

`downloadAttachment` in `@chat-adapter/shared` now resolves `headers`
per hop (pass a function to control what each redirect target receives),
forwards the resolved headers to custom transports, and accepts an
`onResponse` hook to reject unexpected final responses before the body
is read.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/twilio@4.39.0

### Minor Changes

- 75cadbf: feat(twilio): add RCS support with rich cards, button
actions, and location sharing

Extends the Twilio adapter with full RCS support: inbound button tap
routing via `processAction`, location share parsing, Content API
integration for rich outbound cards with SMS fallback, and channel
metadata detection. Cards sent to RCS-capable senders (Messaging Service
or `rcs:` address) are automatically rendered as Twilio Content
templates with embedded SMS fallback variants.

Existing deployments keep their thread ids: plain SMS threads stay keyed
by phone number even when the number belongs to a Messaging Service, and
`openDM` still prefers `phoneNumber` over `messagingServiceSid`. Only
taps of buttons rendered by Chat SDK become actions; foreign button taps
that carry a body keep arriving as messages.

### Patch Changes

- 28bc776: isolate Twilio message processing locks by conversation
- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## chat@4.39.0

### Minor Changes

- 2ce2be0: Add Slack Agent Sessions lifecycle support, native stop
cancellation through `thread.signal`, automatic session titles, and
session stop/title-change events while preserving the legacy
`assistant_view` compatibility path.
- 169788b: Introduce the unified History API (`bot.history`) with user,
thread, and channel scopes.

`bot.history.user` replaces `bot.transcripts` for cross-platform
per-user message persistence. The API surface is identical — migrate by
changing the `transcripts` config key to `history.user` and updating
call sites from `bot.transcripts.*` to `bot.history.user.*`.
`bot.transcripts` remains available as a deprecated alias.

`bot.history.thread` and `bot.history.channel` expose promise-based
helpers for per-thread and per-channel message access, aligned with the
existing `thread.messages` and `channel.threads()` iterators. Reads
delegate to the adapter; adapters that persist history in the SDK-side
store (`persistThreadHistory: true`) are served from that cache. An
unregistered adapter prefix or an unsupported capability throws instead
of returning an empty result.

The new `toPromptEntries` helper converts `history.user.list()` entries
into `{ role, content }` messages ready for LLM prompts.

The `TranscriptEntry` type is deprecated in favour of `HistoryEntry`.
Both are exported from `chat`.

- 5b538f6: Keep thread locks alive while message handlers run so queue,
burst, and debounce strategies remain serialized beyond the lock TTL.
Renewal is capped by the new `concurrency.maxLockLifetimeMs` option
(default 10 minutes) so a hung handler cannot block a thread forever.
When the heartbeat detects that lock ownership was lost, the queue drain
and debounce loops stop instead of competing with the new lock holder,
and the debounce loop now keeps draining messages that arrive while a
handler is running instead of stranding them until the next webhook.

### Patch Changes

- 16ea171: preserve adapter-returned thread ids when editing channel
messages
- eddcd7e: Return Telegram file downloads as portable ArrayBuffer data
while preserving Buffer support in the shared attachment contract.
- 929878b: Allow JSX link buttons to include an explicit action ID.
- 500b7e6: enforce the conversation scope on write tools and stop
trusting client-supplied message history in the web adapter

`createChatTools` now runs the same scope guard on write tools that read
tools already used, so a thread or channel id the model supplies that
resolves outside the scoped conversation is rejected before the write
executes. `sendDirectMessage` targets a user id rather than a
conversation and stays gated by approval alone.

The web adapter no longer treats the request body's `messages` array as
a source of conversation state. Only the latest user message is
consumed, and tool parts are stripped from it so a browser cannot inject
forged tool-call or approval state. Text, file, and custom data parts
pass through unchanged; a message left with no parts after stripping is
rejected with HTTP 400. Prior turns come from the state adapter when
`persistMessageHistory` is enabled.
## @chat-adapter/discord@4.39.0

### Patch Changes

- c4f709f: fix edits, deletes, and reactions on Discord thread starter
messages

Operations on a thread's starter message now try the thread first and
fall back to the parent channel when Discord reports the message as
unknown. Threads on a text channel keep their starter message in the
parent channel, so those operations used to fail; forum and media posts
keep theirs in the thread and are unaffected.

Note that deleting a text-channel thread's starter message now deletes
the message, which Discord cascades into deleting the thread.

- b6fa24c: guard attachment downloads across the remaining adapters

Slack, Discord, and WhatsApp attachment downloads now go through the
shared guarded downloader: private and internal addresses are refused
(as URL literals, through DNS resolution, and after redirects),
responses are capped at 25 MB, and downloads time out after 30 seconds.
Slack sends the bot token only on hops to trusted Slack origins, and
WhatsApp keeps its access token on Meta's media hosts and the configured
Graph origin. Telegram enforces the same size cap and timeout with the
Web Fetch API so downloads keep working in runtimes like Cloudflare
Workers.

`downloadAttachment` in `@chat-adapter/shared` now resolves `headers`
per hop (pass a function to control what each redirect target receives),
forwards the resolved headers to custom transports, and accepts an
`onResponse` hook to reject unexpected final responses before the body
is read.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/gchat@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/github@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/instagram@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/linear@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/messenger@4.39.0

### Patch Changes

- 153bd96: guard Messenger attachment downloads against SSRF and
oversized responses

`downloadAttachment` in `@chat-adapter/shared` accepts an optional
`hosts` allowlist that restricts downloads, including redirect targets,
to the given hosts and their subdomains. The Messenger adapter uses it
to download attachment media only from Meta's `fbsbx.com` and
`fbcdn.net` hosts, with the shared SSRF guard, 25 MB size cap, and 30
second timeout. External fallback and link-share URLs are rejected
before any network request.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/notion@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/teams@4.39.0

### Patch Changes

- 63997ac: hydrate live Teams sender email without requiring Microsoft
Graph permissions
- bb92688: secure anonymous attachment downloads against SSRF and
oversized responses

`@chat-adapter/shared` gains `downloadAttachment`, a guarded downloader
that refuses private and internal addresses (as URL literals, through
DNS resolution, and after redirects), decodes compressed responses, caps
the body size at 25 MB, and bounds the whole download with a 30 second
timeout. All of these are configurable, including the transport for
proxied deployments.

The Teams adapter uses it for anonymous attachment downloads. HTTPS
attachments on any public host keep working, plain-HTTP URLs are
refused, and the Bot Framework Emulator's loopback connector now uses
bot authentication so local development keeps working.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/web@4.39.0

### Patch Changes

- 500b7e6: enforce the conversation scope on write tools and stop
trusting client-supplied message history in the web adapter

`createChatTools` now runs the same scope guard on write tools that read
tools already used, so a thread or channel id the model supplies that
resolves outside the scoped conversation is rejected before the write
executes. `sendDirectMessage` targets a user id rather than a
conversation and stays gated by approval alone.

The web adapter no longer treats the request body's `messages` array as
a source of conversation state. Only the latest user message is
consumed, and tool parts are stripped from it so a browser cannot inject
forged tool-call or approval state. Text, file, and custom data parts
pass through unchanged; a message left with no parts after stripping is
rejected with HTTP 400. Prior turns come from the state adapter when
`persistMessageHistory` is enabled.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/whatsapp@4.39.0

### Patch Changes

- 3e6e866: support business-scoped user IDs for inbound and outbound
WhatsApp messages
- e71bfea: Add `normalizeCodeFences` to `@chat-adapter/shared`: a
code-fence normalizer for platforms whose triple-backtick fences treat
the text after the opening fence as code rather than a CommonMark info
string. Only paired fences become code blocks (unpaired fences, fences
inside inline code, and fences on quoted lines stay literal text), text
following a closing fence cannot be promoted to a block construct, and
per-segment callbacks keep text-level rewrites out of code content.

The WhatsApp adapter now uses it when parsing incoming messages: the
first line of a code block is preserved in message text and formatted
content, and bold/strikethrough rewriting no longer corrupts fenced
code.

- 7c26965: prevent attachment downloads from sending credentials to
untrusted hosts
- b6fa24c: guard attachment downloads across the remaining adapters

Slack, Discord, and WhatsApp attachment downloads now go through the
shared guarded downloader: private and internal addresses are refused
(as URL literals, through DNS resolution, and after redirects),
responses are capped at 25 MB, and downloads time out after 30 seconds.
Slack sends the bot token only on hops to trusted Slack origins, and
WhatsApp keeps its access token on Meta's media hosts and the configured
Graph origin. Telegram enforces the same size cap and timeout with the
Web Fetch API so downloads keep working in runtimes like Cloudflare
Workers.

`downloadAttachment` in `@chat-adapter/shared` now resolves `headers`
per hop (pass a function to control what each redirect target receives),
forwards the resolved headers to custom transports, and accepts an
`onResponse` hook to reject unexpected final responses before the body
is read.

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/x@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [153bd96]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [bb92688]
- Updated dependencies [5b538f6]
- Updated dependencies [e71bfea]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
- Updated dependencies [b6fa24c]
  - chat@4.39.0
  - @chat-adapter/shared@4.39.0
## @chat-adapter/state-ioredis@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [5b538f6]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
  - chat@4.39.0
## @chat-adapter/state-memory@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [5b538f6]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
  - chat@4.39.0
## @chat-adapter/state-pg@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [5b538f6]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
  - chat@4.39.0
## @chat-adapter/state-redis@4.39.0

### Patch Changes

- Updated dependencies [2ce2be0]
- Updated dependencies [16ea171]
- Updated dependencies [169788b]
- Updated dependencies [eddcd7e]
- Updated dependencies [5b538f6]
- Updated dependencies [929878b]
- Updated dependencies [500b7e6]
  - chat@4.39.0
## @chat-adapter/tests@4.39.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@chat-adapter/state-pg@4.39.0 @chat-adapter/web@4.39.0 @chat-adapter/teams@4.39.0 @chat-adapter/discord@4.39.0 @chat-adapter/telegram@4.39.0 @chat-adapter/state-redis@4.39.0 chat@4.39.0 @chat-adapter/gchat@4.39.0 @chat-adapter/github@4.39.0 @chat-adapter/x@4.39.0 @chat-adapter/tests@4.39.0 @chat-adapter/state-memory@4.39.0 @chat-adapter/instagram@4.39.0 @chat-adapter/linear@4.39.0 @chat-adapter/state-ioredis@4.39.0 @chat-adapter/whatsapp@4.39.0 @chat-adapter/twilio@4.39.0 @chat-adapter/slack@4.39.0 @chat-adapter/messenger@4.39.0 @chat-adapter/notion@4.39.0 @chat-adapter/shared@4.39.0
2026-08-28 20:37:34 +10:00
OSS Polar Bear 75cadbf9aa feat(twilio): add RCS support for interactive inbound and rich outbound (#590)
Extend the Twilio adapter with RCS webhook parsing, Content API
integration, and card-to-template mapping with SMS fallback.

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 19:57:44 +10:00
OSS Polar Bear 169788b65a feat(chat): introduce unified History API with user, thread, and chan… (#592)
Adds `bot.history` as the canonical entry point for message history,
with three scopes: `user`, `thread`, and `channel`. `bot.transcripts`
stays as a deprecated alias, so nothing breaks.

## Why

History access was spread across `bot.transcripts`, `thread.messages` /
`thread.allMessages`, and per-adapter calls. `bot.history` puts the
promise-based read paths in one place, and the AI tools
(`fetchMessages`, `fetchChannelMessages`, `listThreads`) now route
through it.

## User scope

Cross-platform per-user persistence, identical in surface to
`bot.transcripts`:

```typescript
const bot = new Chat({
  adapters: { slack, telegram },
  state,
  history: {
    user: {
      identity: ({ author }) => author.email ?? null,
      retention: "30d",
      maxPerUser: 200,
    },
  },
});

await bot.history.user.append(thread, message);
const entries = await bot.history.user.list({ userKey, limit: 20 });
await bot.history.user.delete({ userKey });
```

The new `toPromptEntries` helper turns those entries into `{ role,
content }` messages for an LLM call:

```typescript
import { toPromptEntries } from "chat";

const entries = await bot.history.user.list({ userKey });
const { text } = await generateText({
  model,
  messages: toPromptEntries(entries),
});
```

## Thread scope

Single-page reads and an auto-paginating generator:

```typescript
// One page, newest messages by default
const { messages, nextCursor } = await bot.history.thread.list(thread.id, {
  limit: 20,
});

// Everything, oldest first, pagination handled for you
for await (const msg of bot.history.thread.collect(thread.id, { limit: 50 })) {
  console.log(msg.text);
}
```

## Channel scope

```typescript
// Top-level channel messages (not thread replies)
const { messages } = await bot.history.channel.listMessages("slack:C123", {
  limit: 20,
});

// Thread listings
const { threads } = await bot.history.channel.listThreads("slack:C123");

// Threads together with a page of messages each
const result = await bot.history.channel.listThreadsWithMessages("slack:C123", {
  maxThreads: 5,
  messagesPerThread: 10,
});
```

## Semantics

The read paths are strict about where data comes from:

- The adapter named in the ID prefix must be registered. A typo'd or
unknown prefix throws instead of reading as an empty conversation.
- The SDK-side `ThreadHistoryCache` only serves adapters that persist
history there (`persistThreadHistory: true`, e.g. Telegram, WhatsApp).
For every other adapter the platform response is authoritative, so an
empty page is a real empty page, and a `cursor` always returns the
adapter's response as-is.
- Cache reads honor the same windows as adapter reads: backward
(default) gives the newest N, forward the oldest N, and `collect()`
yields the oldest N on both paths.
- `channel.listMessages` throws a capability error on adapters without
`fetchChannelMessages` (persisting adapters are served from the
channel-keyed cache instead), and `listThreadsWithMessages` fetches
per-thread pages through `history.thread.list` a few threads at a time
to stay inside platform rate limits.

## Migration

```typescript
// Before
const bot = new Chat({
  identity: ({ author }) => author.email ?? null,
  transcripts: { retention: "30d", maxPerUser: 200 },
});
await bot.transcripts.append(thread, msg);

// After
const bot = new Chat({
  history: {
    user: {
      identity: ({ author }) => author.email ?? null,
      retention: "30d",
      maxPerUser: 200,
    },
  },
});
await bot.history.user.append(thread, msg);
```

You can migrate one field at a time: when both `history.user` and the
legacy `transcripts` block are set they merge, with `history.user`
winning field by field, so settings left on `transcripts` keep applying
until you move them. `TranscriptEntry` is deprecated in favour of
`HistoryEntry` (also exported as `UserHistoryEntry`); all deprecated
names keep working in the current major version.

## Included

- New `packages/chat/src/history/` module with unit tests for every
scope
- AI tools rewired to `bot.history`, keeping their scope guards
- The nextjs example uses the new APIs throughout, with Thread History
and Channel History test buttons that exercise every scope
- Docs: `/docs/history` guide, `/docs/api/history` reference,
deprecation callouts on the transcripts pages
- Changeset (`minor` for `chat`)

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 18:47:26 +10:00
dependabot[bot] 4fa1c2bcf9 build(deps-dev): bump postcss from 8.5.25 to 8.5.26 (#795)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.25 to
8.5.26.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.5.26</h2>
<ul>
<li>Fixed <code>list.split()</code> regression (by <a
href="https://github.com/lazerg"><code>@​lazerg</code></a>).</li>
<li>Track symlinks in path protection in source map loading (by <a
href="https://github.com/drengir1"><code>@​drengir1</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.5.26</h2>
<ul>
<li>Fixed <code>list.split()</code> regression (by <a
href="https://github.com/lazerg"><code>@​lazerg</code></a>).</li>
<li>Track symlinks in path protection in source map loading (by <a
href="https://github.com/drengir1"><code>@​drengir1</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/postcss/postcss/commit/07b25773f38f77919f2af02ae3e8896b0deb5988"><code>07b2577</code></a>
Release 8.5.26 version</li>
<li><a
href="https://github.com/postcss/postcss/commit/47de6b9d7c55674cb326c5de7a734a740916defc"><code>47de6b9</code></a>
Update CI</li>
<li><a
href="https://github.com/postcss/postcss/commit/1493a83db7830912316512f55ab6064e7b7dd68e"><code>1493a83</code></a>
Fix Rule#selectors losing the empty selector (<a
href="https://redirect.github.com/postcss/postcss/issues/2129">#2129</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/180db166e250d20e6761b224ae8d8134c9ba3e40"><code>180db16</code></a>
Typo</li>
<li><a
href="https://github.com/postcss/postcss/commit/29e9e00f132c96e46e1de295b816fe88a05354e7"><code>29e9e00</code></a>
Resolve symlinks before the previous-source-map containment check (<a
href="https://redirect.github.com/postcss/postcss/issues/2125">#2125</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/3ba8f84703a884329b58abea579c3615684e0b7e"><code>3ba8f84</code></a>
Update dependencies</li>
<li><a
href="https://github.com/postcss/postcss/commit/87e72f671fd0d401c52822b5226c656632d92ec0"><code>87e72f6</code></a>
Update lock file</li>
<li><a
href="https://github.com/postcss/postcss/commit/caaeeb907e4a816c44a23b00b151882bd02325a1"><code>caaeeb9</code></a>
Upgrade nanoid to fix infinite loop on zero size (<a
href="https://redirect.github.com/postcss/postcss/issues/2124">#2124</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/3609b6f4296952d0b5b9ddae42c8d73ee460c041"><code>3609b6f</code></a>
Explain how to type plugin options</li>
<li><a
href="https://github.com/postcss/postcss/commit/fbad419cbd01cd7a9a1a46413447f2cd9b3fce4a"><code>fbad419</code></a>
docs: show ESM and TypeScript plugin declaration (<a
href="https://redirect.github.com/postcss/postcss/issues/2118">#2118</a>)</li>
<li>See full diff in <a
href="https://github.com/postcss/postcss/compare/8.5.25...8.5.26">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 17:53:22 +10:00
Yevanchen 5b538f6f21 fix(chat): keep thread locks alive during long handlers (#821)
- renew a held thread or channel lock every 10 seconds while a locking
concurrency strategy is running
- stop the heartbeat before releasing the lock, and handle extension
failures without unhandled rejections
- add regression coverage proving `queue`, `burst`, and `debounce`
remain serialized when a handler exceeds the 30-second lock TTL
- keep the existing short TTL, so a crashed process still releases its
lock automatically

Mosoo Agents hit this with Chat SDK's Telegram adapter while waiting on
long-running Codex Agent handlers. Once a handler crossed 30 seconds, a
later Telegram message could acquire an expired channel lock and run
concurrently on the same conversation.

Fixes #685.

---------

Signed-off-by: Yevanchen <cyefan2@gmail.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 17:39:26 +10:00
Amit Vijapur 864d922204 fix(slack): keep alert attachment content on normalized messages (#846)
Fixes #608.

Slack integrations such as Sentry, PagerDuty and GitHub carry their real
payload in an attachment's `title`, `text` and `fields`. The adapter
only read attachments to build link-unfurl previews, so that content
reached nowhere on the normalized `Message`: `msg.text` held only the
top-level one-line summary, and every consumer inherited the gap,
including `thread.messages`, `toAiMessages` and the `chat/ai`
`fetchMessages` tool.

Non-unfurl attachment content is now folded into the text before the AST
is assembled, so both `formatted` and the derived plain text carry it.
This follows the same approach as #817, which preserved pasted tables.

Three decisions worth calling out for review:

- Link unfurls stay excluded, on the same grounds their blocks already
are via `isForeignAttachment`: the content is not the message author's.
- `fallback` is used only when the attachment has neither its own fields
nor blocks. It is otherwise a plain-text stand-in that duplicates
content rendered elsewhere, and including it unconditionally would
inject strings like `[no preview available]` into messages that already
carry table blocks.
- Mentions inside attachment content are resolved on the async path
only, matching how table cells are already handled in `resolvedContent`.

`fields` was missing from the `SlackEvent["attachments"]` type and has
been added.

## Test plan

Three tests added in `packages/adapter-slack/src/index.test.ts`,
covering alert content (title, text and fields), the `fallback`-only
case, and exclusion of unfurl attachments. The alert test asserts both
the sync `parseMessage` and async `parseSlackMessage` paths, following
the existing table-attachment test.

- `vitest run src/index.test.ts` in `packages/adapter-slack`: 419
passed.
- Reverting only the source change and keeping the new tests makes two
of the three fail with the reported symptom (`expected 'New alert' to be
'New alert\n\nTypeError: cannot read p…'`), confirming they exercise the
bug rather than the implementation.
- `tsc --noEmit` clean, `ultracite check` clean on the changed files.
- `pnpm validate` passes except `create-chat-sdk#test`, which fails
identically on `main` at 294b595 in my environment with `Cannot find
package 'chat/adapters'`. That looks like local workspace resolution
rather than anything in this change.

---------

Signed-off-by: Amit Vijapur <vijapuramit@gmail.com>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 16:35:59 +10:00
Akazawa Daisuke e71bfead52 fix(slack): preserve first line of incoming code blocks (#843)
## Summary

fixes #842

normalizes incoming Slack triple-backtick code fences before parsing
them as CommonMark

Slack treats text immediately after an opening fence as code content,
while CommonMark treats it as the fence's info string; putting Slack
fences on their own lines preserves the first code line in both
`message.text` and `message.formatted`

the normalization also separates fences from surrounding text so inline
Slack code blocks are parsed as fenced code blocks instead of regular
Markdown text

## Test plan

- added format-level regression coverage for code starting immediately
after an opening fence and for fences adjacent to surrounding text
- added converter-level assertions for the parsed code node and
extracted plain text
- ran the focused Slack format and Markdown test suites
- ran `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: akkadaska <akkadaska@gmail.com>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 15:09:03 +10:00
I'm Groot 🌳 a18e79224e feat(telegram): describe locations, contacts, polls and dice (#836)
Telegram sends several message kinds with neither text nor a file. They
reached the handler as empty messages: the content was in the payload,
but anything reading `text` saw nothing and could not tell an empty
delivery from a shared location.

A location, venue, contact, poll, dice, game, invoice and story now each
produce a short literal description, in the same place a sticker
produces its emoji:

```
📍 55.75, 37.61
📍 Central Library, 12 Main St
👤 Ada Lovelace +15551234567
📊 Lunch or dinner?
🎲 4
🎮 Corsairs
🧾 Yearly plan — 49.99 USD
📖 Story
```

The wording stays minimal and the structured payload is untouched on the
raw message, so a handler that wants the coordinates or the poll options
still has them.

Two Bot API quirks shape the implementation:

- A venue message also carries a top-level `location` field for backward
compatibility, so the venue check runs first. Otherwise every venue
would render as bare coordinates.
- An invoice's `total_amount` is in the currency's smallest unit, and
the exponent varies per currency
([currencies.json](https://core.telegram.org/bots/payments/currencies.json)):
JPY and Telegram Stars count whole units, BHD, IQD and JOD use three
decimals, everything else two.

`sample-messages.md` gains fixtures for the new kinds, including the
venue with its co-set location and a contact without a `last_name`.

Signed-off-by: grootbro <vadim@ravefox.dev>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 14:16:21 +10:00
I'm Groot 🌳 a0ba986827 feat(telegram): parse stickers and animations (#835)
Based on #834.

A sticker carries no text, so it reached the handler as an empty message
and looked like a delivery that had lost its body. An animation — the
MP4 Telegram sends for a GIF — was not declared on the message type and
was dropped on the floor.

A sticker now reports the emoji it stands for as the message text, plus
an image attachment typed by its real format: WebP for a still one, WebM
for a video sticker, TGS for a Lottie one. An animation arrives as a
video attachment alongside the other media types.

---------

Signed-off-by: grootbro <vadim@ravefox.dev>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 13:03:27 +10:00
I'm Groot 🌳 26a06ca51d feat(telegram): treat a reply to the bot as a mention (#834)
Based on #833.

In a group a bot only sees messages that address it, and people address
a bot by replying to it as often as by typing its handle. The adapter
reported `isMention` for the handle but not for the reply, so a bot went
quiet the moment the conversation moved to replies.

`mentionOnReply` turns that on. **Off by default** — the flag changes
which messages report `isMention`, and a bot that deliberately answers
only explicit mentions should keep the stricter behaviour. It also reads
`TELEGRAM_MENTION_ON_REPLY`, so a deployment can set it without code,
and the key is declared in the adapters catalog.

The check runs before the empty-text guard, so a reply carrying only a
photo or a document counts too.

---------

Signed-off-by: grootbro <vadim@ravefox.dev>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 12:15:32 +10:00
I'm Groot 🌳 d5ebec127b feat(telegram): implement native message replies (#833)
`Thread.reply()` throws `NotImplementedError` on Telegram: the adapter
has no `reply` method, even though the Bot API threads an answer to its
question with `reply_parameters`.

`postMessage` takes an optional reply target and passes it to every send
path — text, rich messages, documents, attachments and both media group
variants — and `reply()` delegates to it, the same shape the WhatsApp
adapter uses for this contract. The target is decoded through the
existing `decodeCompositeMessageId`, so a target from another chat is
rejected exactly as an edit would be.

`allow_sending_without_reply` is set: a deleted target degrades to an
unthreaded message instead of failing the send.

Three tests cover it: the reference lands on a reply, a plain
`postMessage` stays unthreaded, and a target from another chat is
refused.

---------

Signed-off-by: grootbro <vadim@ravefox.dev>
Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 01:16:28 +10:00
Mahdi Jaafar 500b7e6d2c fix(web): prevent tool approval bypass via client-supplied messages array (#857)
Hardens two trust boundaries reported against the framework: the web
adapter derived conversation state from the client-supplied
`body.messages` array, and the AI SDK write tools skipped the
conversation scope check that read tools already enforced.

## Web adapter: client-supplied messages

`handleWebhook` previously accepted the full `useChat` `messages` array
from the browser. A client could forge tool-call and approval parts in
it, and handlers reading `message.raw` would see that forged state as if
the server had produced it.

The adapter now:

- consumes only the latest user message and ignores the rest of the
array
- strips tool parts from that message, so forged tool-call or approval
state never reaches handlers; text, file, and custom `data-*` parts pass
through to `message.raw` unchanged
- returns 400 when nothing usable remains after stripping
- no longer passes `originalMessages` to `createUIMessageStream`
(nothing registers `onFinish`, so it was never consumed; prior turns
come from the state adapter via `persistMessageHistory`, never from the
request body)

## AI SDK tools: scope on writes

`createChatTools` now runs the same scope guard on write tools that read
tools already used. A thread or channel id the model supplies that
resolves outside the scoped conversation is rejected before the write
executes. The guard is threaded through each tool factory
(`ToolOptions.guard`) rather than wrapped around `execute`, so it is
typed against each tool's input schema and a future tool can't ship
unguarded.

`sendDirectMessage` targets a user id rather than a conversation, so the
guard has nothing to check it against; it stays gated by approval, and
the docs now say so explicitly.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 00:01:50 +10:00
Ben Sabic b6fa24c68f fix(adapters): guard attachment downloads across slack, discord, telegram, and whatsapp (#865)
Follows up on #850, #856, and #859 by adopting the shared guarded
downloader (`downloadAttachment` in `@chat-adapter/shared`) in the
remaining adapters that fetch attachment bytes from event-supplied URLs.

- Slack, Discord, and WhatsApp attachment downloads now refuse private
and internal addresses (as URL literals, through DNS resolution, and
after redirects), cap responses at 25 MB, and time out after 30 seconds.
- Slack sends the bot token only on hops to trusted Slack origins, so a
redirect can never carry it to another host, and keeps the
HTML-login-page detection. A protected `createFileTransport()` override
routes downloads through a proxy.
- WhatsApp keeps its access token on Meta's media hosts, and the
configured Graph origin via the hosts allowlist; `downloadMedia()`
accepts a custom transport.
- Telegram keeps downloads on the Web Fetch API because a downstream
Cloudflare Workers consumer depends on portability (#828), enforcing the
same 25 MB cap and 30-second timeout with web streams.
- `downloadAttachment` now resolves `headers` per hop (function form
decides what each redirect target receives), forwards the resolved
headers to custom transports, and accepts an `onResponse` hook that can
reject a final response before its body is read.
- Adds "Inbound attachments" docs sections for all four adapters.

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-27 13:05:06 +10:00
Ben Sabic 2ce2be008f feat(slack): add Agent Sessions lifecycle and native stop (#862)
Migrates Slack's `agent_view` integration to the Agent Sessions
lifecycle while preserving the legacy `assistant_view` compatibility
path.

- Adds `agents.sessions.setStatus` and `agents.sessions.rename` support
for processing, active, suspended, and closed sessions.
- Handles `agent_session_stopped` without taking the message lock,
clears Slack's processing state, and dispatches `onAgentSessionStopped`.
- Adds cross-process turn cancellation through the configured state
adapter and exposes the active turn as `thread.signal`.
- Handles `agent_session_title_changed` and automatically titles new
agent conversations from their root message, with a configurable
resolver.
- Propagates `session_status` through native stream completion and
supports suspended human-in-the-loop turns.
- Updates Slack manifests, examples, API docs, fixtures, and migration
guidance for the February 2027 `assistant_view` retirement.

Configure the Agent messaging experience and optional title resolver:

```ts
const slack = createSlackAdapter({
  agentView: true,
  sessionTitle: ({ text }) => text.split("\n", 1)[0]?.slice(0, 80) ?? null,
});
```

Pass the thread signal into model generation so Slack's native stop
button cancels upstream work as well as message delivery:

```ts
bot.onDirectMessage(async (thread, message) => {
  await thread.startTyping();

  const result = await agent.stream({
    prompt: message.text,
    abortSignal: thread.signal,
  });

  await thread.post(result.fullStream);
});
```

React to session lifecycle events:

```ts
bot.onAgentSessionStopped(async (event) => {
  await releaseExternalResources(event.threadId);
});

bot.onAgentSessionTitleChanged(async (event) => {
  await syncTitle(event.threadId, event.title);
});
```

Leave a stream suspended when the agent needs user input or approval:

```ts
await thread.post(
  new StreamingPlan(result.fullStream, {
    sessionStatus: "suspended",
  })
);
```

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-27 10:03:14 +10:00
christopherkindl 50af1605d5 chore(docs): use geistdocs 1.23.1 (#864)
Uses `@vercel/geistdocs@1.23.1`, which includes the desktop navbar fix:
clicking an open navigation trigger closes its menu.

Release:
https://github.com/vercel/geistdocs/releases/tag/%40vercel%2Fgeistdocs%401.23.1

## Validation
- `pnpm install --lockfile-only --ignore-scripts`
- `git diff --check`
2026-08-25 21:30:51 +10:00
josh 153bd9640d fix(messenger): guard attachment downloads (#856)
## summary

- restrict Messenger attachment downloads to Meta's `fbsbx.com` and
`fbcdn.net` hosts while preserving external URLs on `attachment.url`
- reject untrusted URLs before connecting using HTTPS validation,
connection-bound DNS checks, manual redirect validation, timeouts, and
streamed size limits
- move the guarded downloader into `@chat-adapter/shared` and keep the
Teams implementation behaviorally equivalent
- normalize malformed redirect locations and other download failures as
typed `NetworkError` values
- document the inbound attachment policy for Messenger
- stacked on #850 and should merge after it

## test plan

- verified valid Meta image, audio, video, and file CDN hosts remain
downloadable
- verified external hosts, private addresses, malformed URLs, unsafe
ports, trailing dots, and suffix attacks are rejected
- verified mixed private and public DNS results fail closed
- verified redirects are revalidated and malformed or external
destinations are rejected
- verified declared and streamed size limits and stalled body timeouts
- ran workspace build, affected package tests and typechecks,
integration checks, Knip, Ultracite, and diff validation

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-25 21:30:21 +10:00
josh bb926884a2 fix(teams): secure attachment downloads (#850)
## summary

- restrict anonymous attachment downloads to current Microsoft 365
SharePoint and OneDrive for Business hosts
- reject internal addresses using connection-bound DNS validation
- revalidate every redirect and disable connection reuse outside the
guarded transport
- enforce a 25 MB streaming response limit and a 15 second request
timeout
- preserve connector-origin bot authentication and the protected custom
fetch override
- document the default anonymous download policy

## test plan

- verify trusted Microsoft 365 attachment hosts remain supported
- verify HTTP, custom ports, lookalike domains, trailing-dot hosts, and
generic off-origin URLs are rejected
- verify private IPv4, encoded IPv4, bracketed IPv6, and mixed DNS
results are rejected
- verify redirects are revalidated before another request
- verify oversized streamed responses are stopped
- verify activity parsing and attachment rehydration use the guarded
transport
- run Teams tests, typecheck, formatting, and production builds

---------

Signed-off-by: dancer <josh@afterima.ge>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-25 21:09:08 +10:00
Max eddcd7e46b fix(telegram): return portable file data (#828)
## Summary

Telegram file downloads already receive their bytes from the Web Fetch
API as an `ArrayBuffer`, but the adapter immediately converts them with
`Buffer.from(...)` before returning. That conversion is unnecessary for
consumers that accept web-standard binary data, and it throws when the
Node `Buffer` global is unavailable. The [Fetch
standard](https://fetch.spec.whatwg.org/#dom-body-arraybuffer) defines
`Response.arrayBuffer()` as returning an `ArrayBuffer`; Cloudflare
Workers exposes the [Fetch API
natively](https://developers.cloudflare.com/workers/runtime-apis/fetch/),
while `Buffer` belongs to its [Node.js compatibility
surface](https://developers.cloudflare.com/workers/runtime-apis/nodejs/buffer/).

This change returns the fetched `ArrayBuffer` directly from Telegram.
The shared `Attachment.fetchData` and protected Telegram method use
`Buffer | ArrayBuffer` so existing adapters and subclasses that return
`Buffer` remain source-compatible. The two consumers of that contract
now accept the portable value: `chat/ai` passes `ArrayBuffer` directly
to the AI SDK, and the X adapter normalizes either type at its
Buffer-based upload boundary. The public file documentation and patch
changesets are updated with the same contract.

The downstream evidence is a pnpm patch in the private Calories
Cloudflare Workers consumer at
`patches/@chat-adapter__telegram@4.36.0.patch`. Its portability hunk
changes `downloadFile` from `Promise<Buffer>` to `Promise<ArrayBuffer>`
and changes `Buffer.from(await response.arrayBuffer())` to
`response.arrayBuffer()`; the other Telegram hunks in that patch are
already upstream and are intentionally excluded here.

## Test plan

- `pnpm validate`
- `pnpm --filter @chat-adapter/telegram test` (269 tests)
- `pnpm --filter @chat-adapter/telegram typecheck`
- `pnpm --filter chat test` (1,131 tests)
- `pnpm --filter chat typecheck`
- `pnpm --filter @chat-adapter/x test` (222 tests)
- `pnpm --filter @chat-adapter/x typecheck`
- Added a regression test that removes the global `Buffer`, exercises
Telegram's mocked `getFile` and file-fetch path, and asserts the
returned bytes are an `ArrayBuffer`.

The runtime proof is limited to the isolated download seam under Node
with `Buffer` removed. This PR does not claim a deployed
no-compatibility Cloudflare Worker or a live Telegram end-to-end
request.

## 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-08-25 21:03:46 +10:00
Max 63997acaa8 fix(teams): hydrate incoming users without Graph (#860)
## Summary

Changes live incoming Teams author hydration to
`ctx.api.conversations.getMemberById`, so the normal path no longer
requires Microsoft Graph's `User.Read.All` permission or tenant admin
consent. Explicit `getUser()` lookups remain Graph-backed.

## Test Plan

- `pnpm --filter @chat-adapter/teams test` (264 passed)
- `pnpm --filter @chat-adapter/teams exec vitest run src/index.test.ts
-t 'incoming sender email'` (8 passed)
- `pnpm --filter @chat-adapter/teams typecheck`
- `pnpm --filter @chat-adapter/teams... build`
- `pnpm check`
- `git diff --check`
- built and packed `@chat-adapter/teams`; inspected the artifact for
both the Connector lookup and preserved Graph lookup

The regression tests assert the exact activity conversation and sender
IDs, Graph isolation on Connector success and failure, cache behavior,
the missing-AAD fallback, and the DM path. A live Microsoft Teams tenant
was not available for runtime verification.

## Checklist

- [x] All commits are signed and verified
- [x] All commits are signed off for the DCO (`git commit -s`)
- [ ] `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-08-25 20:45:27 +10:00
christopherkindl ea716568fa chore(docs): update geistdocs to 1.24.0 (#863)
Updates the docs app to `@vercel/geistdocs@1.24.0` and refreshes the
pnpm lockfile.

Includes improved agent recovery and discovery from
https://github.com/vercel/geistdocs/pull/255.

## Validation
- `pnpm install --lockfile-only --ignore-scripts`
- `git diff --check`
2026-08-25 20:16:22 +10:00
josh 28bc776858 fix(twilio): isolate message locks by conversation (#849)
## summary

- use thread-scoped locking so separate Twilio conversations no longer
contend for the same sender lock
- preserve the existing Twilio channel ID format and channel behavior
- add regression coverage proving concurrent recipients process
independently

Signed-off-by: dancer <josh@afterima.ge>
2026-08-25 20:04:31 +10:00