chore: scaffold five new backend packages (postgres, zulip, discord, telegram, slack)#2
Merged
Merged
Conversation
…telegram, slack) Package skeletons only (package.json + tsconfig.json), lead-owned so the shared package-lock.json is written once. Implementations follow per-package. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
…, LISTEN/NOTIFY push) BIGSERIAL seq serves as both cursor and backendMsgId; post() serializes per-topic via pg_advisory_xact_lock so seq order matches visibility order. subscribe is true event-driven push: a dedicated LISTEN client on parley_<md5(topic)> channels, draining seq > lastSeen on each notify (payload treated as a hint only) with reconnect re-LISTEN + re-drain. Senders table backs resolveIdentity. Conformance green (6/6, including concurrent multi-instance writers) against a live local Postgres 16. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
…gateway push) Message snowflakes serve as both cursor and backendMsgId (per-channel strictly increasing; exclusive-since delegated to the REST ?after= param, BigInt compare where local ordering is unavoidable). subscribe speaks a minimal gateway subset (HELLO/IDENTIFY/heartbeat/MESSAGE_CREATE) over one shared websocket; reconnect re-IDENTIFYs and leans on cursor catch-up instead of RESUME. Conformance green (6/6) against an in-process fake gateway+REST server; hosted-SaaS positioning noted in-code per the brief. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
…ursor, event-queue push) Zulip's globally monotonic message ids serve as both cursor and backendMsgId; exclusive-since is server-side via anchor+include_anchor=false narrowed to stream+topic. Parley topics map near-1:1 onto Zulip topics within one configured stream (topic mutability caveat documented). subscribe registers a narrowed event queue and long-polls /events; queue GC (BAD_EVENT_QUEUE_ID) recovers by re-registering plus a cursor gap-fill. Conformance green (8 passed) against an in-process urlencoded-only fake; a real-server suite is env-gated behind PARLEY_ZULIP_URL/EMAIL/API_KEY. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
… Socket Mode push) Per-channel message ts doubles as cursor and backendMsgId, compared integer-wise (seconds then suffix) — never lexically or as floats. fetchRecent assembles the full (since, now] range across newest-first next_cursor pages, sorts ascending, and takes the earliest limit so a long backlog's middle is never skipped. subscribe rides one Socket Mode websocket (single-use URLs, fresh apps.connections.open per reconnect) and acks every envelope before processing. Conformance green (7 passed, incl. concurrent writers and an ack-discipline test) against an in-process fake; hosted-SaaS positioning and free-plan retention caveat noted in-code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
…_id cursor, local observed store) backendMsgId is the composite <chat_id>:<message_id> (message_id is only per-chat unique); the cursor is the per-chat message_id, monotonic within a topic since a topic maps to exactly one chat. The Bot API exposes no history endpoint, so fetchRecent replays an append-only JSONL store of observed messages (own sendMessage responses + getUpdates) — pre-join history cannot be backfilled, the one structural strain on the seam's durable-history line, documented in code and README. One getUpdates long-poll per token (a second poller 409s), so the multi-writer conformance case is skipped by design. Conformance + inbound-flow tests green (6 passed, 1 skipped). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
…ADME tables, dev-compose) Serialized integration after the parallel per-package builds: root tsconfig references + vitest aliases for the five new packages; DESIGN §6 cursor list, §12 v0.6 build-order block, §13 tree, §18 tags; README backend table (with hosted-SaaS markers and the Telegram no-pre-join-history caveat), status blurb, keywords; dev-compose postgres service + upstream docker-zulip pointer; CONTRIBUTING note on in-process fakes for SaaS backends; PROGRESS v0.6 entry. Full suite: 135 passed / 7 skipped; bridge-core diff vs main is empty — the seam held. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8
sharpTrick
added a commit
that referenced
this pull request
Jul 3, 2026
## What & why Adds **`parley_list_users`** — an MCP tool that reports who is **live** on the bus, with an optional glob filter (e.g. `claude-*`), for picking a hand-off target. This started as "should *list users* be a core seam method every backend implements?" The answer was **no**: the seam is six methods on purpose, several backends have no enumerable user set (Redis/NATS/Discord/Telegram), and a mandatory `listUsers` would mean four different things wearing one name — the failure the conformance suite exists to prevent. The better design (Patrick's insight): **make presence a message.** Each bridge announces `hello` / `heartbeat` / `goodbye`, and liveness is derived **entirely above the seam** from `post`/`fetchRecent` + a TTL window. ## Result - **Works identically on every backend** — no new seam method. - **Lists an idle instance that has never posted** (the case a sender-scan misses). - **Zero changes to any backend plugin; the conformance suite is unchanged** — the repo's own proof that prime directive #2 held. - Presence streams are **isolated**: derived presence topics are never subscribed and never enter catch-up/dedup, so heartbeats never surface as `<channel>` events or pollute durable history. - Reports **Parley-participant liveness, not a human directory** — a human in a native client appears once they send a real message. ## Design decisions (locked with the user) - **Dedicated presence stream** (not heartbeats in real topics) — keeps durable history and push clean, no `Message`-model change. - **Presence-only, uniform** (not presence + per-backend directory) — keeps the "same behavior everywhere" property. ## Changes (all in `bridge-core`) - `engine/presence.ts` — `presenceTopicFor`, encode/decode, `computeLive` (TTL liveness) - `transport/presence-loop.ts` — best-effort hello/heartbeat/goodbye emitter - `identity-filter.ts` — `matchGlob` / `filterHandles` - `transport/tools.ts` — `parley_list_users` tool (+ `ToolDeps` gains `presenceTtlMs`/`now`) - lifecycle wiring: `stdio-bridge` (`attach`/`shutdown`) + `http` (app scope) - `config.ts` — `presence` block (`enabled`/`heartbeat_ms`/`ttl_ms`) - docs: DESIGN §7/§11, README; fixed the stale §4 "real account lookup for Matrix/XMPP" note (both echo the handle today) ## Testing - **154 tests pass**, typecheck clean. 25 new unit tests (presence, glob, emitter loop, 6 tool cases). - **End-to-end on a real SQLite backend through the full bridge lifecycle** — all 7 checks passed: idle agent discoverable via presence alone; filter narrows; real messages never pollute the presence stream or invent a user; clean-shutdown `goodbye` drops fast; a stale/crashed heartbeat is reclaimed by TTL. ## Known follow-up - Presence streams grow append-only (heartbeat every 30s). `list_users` reads a bounded recent window; retention/pruning is a future optimization (the seam has no delete) — noted, not blocking. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Package skeletons only (package.json + tsconfig.json), lead-owned so the
shared package-lock.json is written once. Implementations follow per-package.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01QTSH1UJmMGg7VG6diAfcG8