* Initial work on postgres state store
* Run fix
* Replace pre-written CHANGELOG with proper changeset
The CHANGELOG was manually written with a version entry. This repo uses
Changesets to manage versioning, so add a proper changeset file instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add missing @vitest/coverage-v8 dev dependency
The vitest config specifies coverage provider "v8" but the package
was missing from devDependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Drop drizzle-orm, use raw postgres queries
The adapter only needs 3 simple tables with basic CRUD. drizzle-orm is
a full ORM that adds significant dependency weight for no real benefit
here. The ensureSchema() method was already using raw queries. This
halves the dependency surface to just the postgres driver.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add comprehensive unit tests for postgres state adapter
Test factory function edge cases (missing URL, env var fallbacks,
custom keyPrefix) and ensureConnected guard for all state operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Document expired row cleanup limitation for postgres adapter
Unlike Redis, Postgres doesn't auto-delete expired rows. Document the
opportunistic cleanup behavior and provide SQL for periodic cleanup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove stale drizzle keyword from package.json
drizzle-orm was removed in a prior commit but the keyword remained.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix expiresAt parsing in acquireLock
The postgres library returns timestamptz columns as JavaScript Date
objects, not strings. Wrapping in new Date() was unnecessary overhead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Document lock atomicity difference vs Redis adapters
The Postgres ON CONFLICT approach relies on row-level locking rather
than a single atomic SET NX PX like Redis. Note this for users who
need high-contention distributed locking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use crypto.randomUUID() for lock token generation
Math.random() is not cryptographically secure and has a higher
collision risk in distributed environments. crypto.randomUUID() is
available in Node 16+ and provides better uniqueness guarantees.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Delete CHANGELOG.md
* Add comprehensive unit tests for postgres state adapter
Mock the postgres module and SQL client to achieve 100% coverage
across statements, branches, functions, and lines. Tests cover
factory function, connection lifecycle, subscriptions, locking,
cache operations, and the owned-client disconnect path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add setIfNotExists method and merge with main
Implement the setIfNotExists method added to the StateAdapter
interface since this branch diverged. Uses INSERT ... ON CONFLICT
DO NOTHING with RETURNING to atomically check-and-set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add postgres to state docs navigation and memory adapter callout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Migrate postgres state adapter from postgres to pg (node-postgres)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename state-postgres to state-pg and align version to 4.17.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Hayden Bleasel <hello@haydenbleasel.com>
Co-authored-by: Claude Opus 4.6 <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>
* Add support for telegram bot
* Add support for action buttons in telegram
* Use markdown parse mode
* Add more tests
* Fix linting issues
* Fixes
* Fixes
* Don't reverse order messages for AI and always answer for telegram
* Add telegram integration test
* Update telegram.json
* feat(slack): enable DM support with per-thread IDs and implicit mentions
DMs now use per-message threadTs (same as channels) instead of collapsing
all DM messages into one thread. Each top-level DM starts a new conversation;
thread replies continue the same conversation.
DM messages automatically set isMention=true so bots process them without
requiring @mention — natural for 1:1 conversations.
* fix: use empty threadTs for top-level DMs, thread_ts for replies
Top-level DM messages use empty threadTs to match openDM() subscriptions.
DM thread replies use thread_ts for per-conversation isolation.
Fixes integration test replay-dm.test.ts.
* style: fix biome formatting
* test: add e2e replay tests for direct DM implicit mention
* style: fix extra blank line lint error
* test: use real recorded DM fixtures for e2e tests
* [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>