* Fix JSX issues
* fix: add null guards for event.thread in onAction handlers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(slack): support streaming task_update, plan_update, markdown_text chunks
- Add StreamChunk type for structured streaming content
- Extend stream() to accept AsyncIterable<string | StreamChunk>
- Add taskDisplayMode option to StreamOptions
- Slack adapter passes structured chunks directly to Slack's streaming API
- Handle url_verification before signature check to prevent Slack from
silently disabling event delivery when signing secret is misconfigured
- Add tests for StreamChunk handling in thread.post()
* review fixes: type safety, security, dedup
- Replace loose StreamChunk interface (index signature) with
discriminated union (MarkdownTextChunk | TaskUpdateChunk | PlanUpdateChunk)
- Move url_verification back after signature check to prevent
unauthenticated challenge responses
- Remove redundant AsyncIterable<string> from PostableMessage union
- Extract pushTextAndFlush helper to deduplicate markdown flush logic
- Remove unnecessary `as string` casts after type guards
- Export individual chunk types from package index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* add changeset
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix openModal crash when action has no thread context
Home tab actions (e.g. Change Team) have no thread → openModal closure
crashed accessing thread.channel on null. Guard thread access in the
openModal closure and pass undefined to storeModalContext.
Add test for empty threadId action → openModal works with null thread.
* add changeset
* make ActionEvent.thread nullable, remove unsafe cast
- Change `ActionEvent.thread` type from `Thread` to `Thread | null`
- Remove `null as unknown as Thread` unsafe cast in handleActionEvent
- Update test files to use null guards and optional chaining
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve nesting depth when rendering lists in all chat adapters
All format converters (Slack, Discord, Teams, Google Chat) were flattening
nested lists during Markdown→platform conversion. When a listItem had a
paragraph followed by a child list, the child list output was concatenated
directly onto the parent item's text with no indentation or separating
newline, producing garbled output like "• parent• child 1\n• child 2".
Add an optional `depth` parameter to each adapter's private `nodeToX()`
method. The `isListNode` handler now builds lines explicitly: paragraph
content is prefixed with the bullet/number at the correct indent level
(`" ".repeat(depth)`), and nested list nodes are rendered recursively
at `depth + 1`. Sibling items at the same depth are unaffected.
Add regression tests covering:
- nested unordered lists (2 levels)
- nested ordered lists (2 levels)
- deeply nested lists (3 levels)
- flat sibling items (no regressions)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: extract renderList() helper to eliminate dead depth parameter
The previous fix added `depth = 0` to each adapter's general nodeToX()
method, but only the isListNode branch ever used it — a dead parameter
across ~10 other branches that made the signature misleading.
Extract a private renderList(node: List, depth: number) method in each
adapter. nodeToX() stays as a clean single-argument node converter;
all depth-aware list logic lives exclusively inside renderList(), which
recurses into itself for nested lists and calls nodeToX() for leaf nodes.
Import the List type from the chat package to give renderList() an
explicit, narrowed parameter type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Deduplicate renderList into BaseFormatConverter, fix start offset, add mixed list tests
- Extract shared renderList() into BaseFormatConverter with configurable bullet char
- Respect List.start property for ordered lists starting at non-1 values
- Remove dead isListItemNode branches from all adapters
- Add mixed ordered/unordered nesting test to all adapters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
* feat(chat, adapter-gchat): add disabled prop to Button
Google Chat Cards v2 natively supports `disabled` on buttons, but
`ButtonOptions` / `ButtonElement` didn't expose it. Add the prop to
the core type and thread it through the gchat adapter serializer.
Closes#149
Made-with: Cursor
* feat(adapter-discord): thread disabled prop through Discord button serializer
DiscordButton already had `disabled` in its type but convertButtonElement
never set it from ButtonElement. Wire it through so disabled buttons
render correctly on Discord too.
Made-with: Cursor
* feat(chat): allow disabling fallback streaming placeholder
Add ChatConfig options to delay initial post+edit placeholder and wait for real text before creating the streamed message. Improves ordering for Telegram and other fallback-streaming adapters.
Made-with: Cursor
* fix(chat): simplify fallback streaming placeholder
* Create fallback-streaming-placeholder.md
* Improve test
* Add new test
* Run fix
* Update streaming.mdx
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
* feat(chat): make message dedup TTL configurable to prevent Slack retry duplicates
* changeset
* fix
* bump default to 5 min
* fix
* Fix: Test, JSDoc, and README all reference the old default dedupe TTL of 60,000ms after the constant was changed to 300,000ms (5 minutes), causing test failure.
This commit fixes the issue reported at packages/chat/src/chat.test.ts:140
**Bug:**
The `DEDUPE_TTL_MS` constant in `packages/chat/src/chat.ts` line 51 was changed from `60_000` (60 seconds) to `5 * 60 * 1000` (300,000 ms = 5 minutes). However, three other locations were not updated to match:
1. **Test (`chat.test.ts` line 140):** The test "should use default dedupe TTL of 60 seconds" asserts that `mockState.set` is called with `60_000` as the TTL. Since the actual code now uses `300_000`, the test fails — confirmed by running the test suite which shows 1 failed test: expected `60000` but received `300000`.
2. **JSDoc (`types.ts` line 40):** The documentation comment says "Defaults to 60000 (60 seconds)" which is incorrect — it's now 300000 (5 minutes).
3. **README.md (line 52):** The Configuration table lists the default as `60000` which is now wrong.
**Evidence:** Running `npx vitest run` shows 1 test failure out of 443 tests. After applying the fix, all 443 tests pass.
**Fix:**
1. Updated the test in `chat.test.ts`: Changed the test description from "60 seconds" to "5 minutes" and the expected value from `60_000` to `300_000`.
2. Updated the JSDoc in `types.ts`: Changed "Defaults to 60000 (60 seconds)" to "Defaults to 300000 (5 minutes)".
3. Updated the README.md: Changed the default value in the Configuration table from `60000` to `300000`.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: NicolasMontone <montonenicolas01@gmail.com>
* fix
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
* [chat] add Channel abstraction as first-class citizen alongside Thread
Introduces a Channel concept — the container that holds threads — enabling
channel-level operations, thread-to-channel navigation, and natural support
for threadless platforms (iMessage, WhatsApp, SMS).
Design:
- Postable base interface: shared by both Thread and Channel, providing id,
adapter, isDM, state/setState, messages (newest-first async iterator),
post, postEphemeral, startTyping, mentionUser. Enables polymorphic code
that works with either entity.
- Channel interface (extends Postable): adds name, threads() async iterator,
and fetchInfo(). ChannelImpl provides the concrete implementation with
backward message pagination, thread listing, posting, state management
(channel-state: key prefix), and workflow serialization (chat:Channel).
- Thread updates: Thread now extends Postable and gains a `channel` getter
(lazy-created, cached ChannelImpl) and a `messages` getter (newest-first
backward iterator) alongside the existing `allMessages` (oldest-first).
- Channel ID format: derived from thread IDs by dropping the thread part.
Default: first two colon-separated parts (slack:C123, gchat:spaces/ABC).
Adapters override via optional channelIdFromThreadId() method.
- Adapter interface additions (all optional): channelIdFromThreadId,
fetchChannelMessages, listThreads, fetchChannelInfo, postChannelMessage.
- Chat class: chat.channel(channelId) method creates a Channel by ID,
auto-inferring adapter from the prefix. Reviver handles chat:Channel.
- Slack adapter: implements all five channel methods using
conversations.history (vs conversations.replies for threads),
conversations.info, and chat.postMessage without thread_ts.
- Mock adapter: updated with all channel methods for testing.
- 32 new tests covering ChannelImpl, thread.channel, deriveChannelId,
and the backward messages iterator.
Usage:
// Navigate from thread to channel
const channel = thread.channel;
// Get a channel directly
const channel = chat.channel("slack:C123ABC");
// Iterate messages newest first
for await (const msg of channel.messages) { ... }
// List threads in a channel
for await (const t of channel.threads()) { ... }
// Post to channel top-level
await channel.post("Hello channel!");
TODO:
- Add replay tests for channel operations against recorded webhook data
- Implement channel methods in non-Slack adapters (GChat, Teams, Discord,
GitHub, Linear) — currently they fall back to the default/no-op behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* channel-post-sample
* Better metadata
* Fix bug
* Fixes
* Other adapters
* Fix-gchat
* Fix-discord
* Fix-discord-threads
* Discord replay
* Teams tests and fix pre-existing failures
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>