feat(cli): add provider-agnostic channel bridge core#109
feat(cli): add provider-agnostic channel bridge core#109cdenneen wants to merge 12 commits intohappier-dev:devfrom
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a provider-agnostic Channel Bridge: a tick-driven worker that pulls inbound messages from adapters, parses slash-commands, manages channel↔session bindings and cursors, forwards user/agent messages bidirectionally, and includes an in-memory binding store, deduper, tests, and docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Adapter as Channel Adapter
participant Worker as Channel Bridge Worker
participant Store as Binding Store
participant Deps as Session Deps
rect rgba(100,150,240,0.5)
Note over Adapter,Worker: Periodic tick pulls inbound messages
end
Adapter->>Worker: pullInboundMessages()
Worker->>Worker: parseSlashCommand / classify message
alt Slash command (e.g., /attach)
Worker->>Deps: resolveSessionIdOrPrefix / listSessions
Worker->>Store: upsertBinding / removeBinding
Worker->>Adapter: sendMessage (UI hint / failures)
else User message (bound)
Worker->>Store: getBinding(ref)
Worker->>Deps: sendUserMessageToSession(sessionId, message)
else User message (unbound)
Worker->>Adapter: sendMessage (no-binding hint)
end
Worker->>Store: listBindings()
loop For each binding
Worker->>Deps: resolveLatestSessionSeq(sessionId)
Worker->>Deps: fetchAgentMessagesAfterSeq(sessionId, afterSeq)
Deps-->>Worker: agent messages
Worker->>Adapter: sendMessage (agent reply)
Worker->>Store: updateLastForwardedSeq(ref, newSeq)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR introduces a provider-agnostic channel bridge core — a robust 31KB worker implementation backed by a comprehensive 57KB test suite. The implementation is the product of extensive multi-round review; all previously identified issues have been fully addressed. Verified core components:
All previously identified risks addressed:
Test suite is comprehensive, condition-based (avoids timing-sensitive fixed sleeps), covers error paths exhaustively, and properly isolates deduper state per test. Confidence Score: 5/5
Last reviewed commit: e315c14 |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
apps/cli/src/channels/core/channelBridgeWorker.test.ts (1)
41-203: Add lifecycle coverage forstartChannelBridgeWorker(triggerandstop).
executeChannelBridgeTickis covered well, but this PR also introduces a public worker lifecycle API. Add tests for initial trigger behavior, stop idempotency, and adapterstop()propagation.As per coding guidelines: "Write unit tests for all public functions and components".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/cli/src/channels/core/channelBridgeWorker.test.ts` around lines 41 - 203, Add unit tests for the new public lifecycle API startChannelBridgeWorker: write a test that calls startChannelBridgeWorker(...) then calls trigger() once to ensure the worker runs its first tick (you can assert by spying on executeChannelBridgeTick or deps called), a test that calls stop() twice to verify idempotency, and a test that ensures adapter.stop() is called when stop() is invoked (spy on the adapter harness' stop method). Use the existing createAdapterHarness(), in-memory store, and deps mocks from the file to locate where to hook spies and assertions; reference startChannelBridgeWorker, trigger, stop, and adapter.stop to find the right symbols to exercise and assert.apps/cli/src/channels/core/channelBridgeWorker.ts (1)
1-368: Add module and exported API JSDoc comments.This file defines public bridge contracts and complex flow control, but there’s no file header/API JSDoc. Please document responsibilities, expected adapter behavior, and cursor semantics to reduce integration errors.
As per coding guidelines: "Include comprehensive JSDoc comments as file header comments explaining file responsibilities" and "Add JSDoc comments for public APIs and complex logic".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/cli/src/channels/core/channelBridgeWorker.ts` around lines 1 - 368, Add a file-level JSDoc header describing the module responsibility (bridging channel adapters to sessions, command handling, and agent->channel cursor semantics) and add JSDoc comments for each public exported type/function: ChannelBridgeAdapter (describe pullInboundMessages/sendMessage/optional stop contract and expected error handling), ChannelBindingStore and ChannelSessionBinding (explain lastForwardedSeq meaning and that updateLastForwardedSeq stores the highest seq forwarded), ResolveSessionIdResult, createInMemoryChannelBindingStore (note now() usage and normalization of seqs), executeChannelBridgeTick (document flow: inbound pull -> commands -> forward user messages -> fetchAgentMessagesAfterSeq where afterSeq is exclusive and returned messages should have seq > afterSeq), and startChannelBridgeWorker (document tickMs bounds, SingleFlightIntervalLoop usage and trigger/stop behavior); ensure comments mention expected behavior of deps methods (resolveLatestSessionSeq, fetchAgentMessagesAfterSeq) and error/warning callbacks so integrators know contracts and cursor semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/cli/src/channels/core/channelBridgeWorker.test.ts`:
- Around line 46-58: The test currently uses vi.fn() mocks for ChannelBridgeDeps
(listSessions, resolveSessionIdOrPrefix, fetchAgentMessagesAfterSeq, etc.);
replace these mocked spies with lightweight real/test implementations of the
ChannelBridgeDeps interface (for example in-memory or fixture implementations
that implement listSessions, resolveSessionIdOrPrefix, sendUserMessageToSession,
resolveLatestSessionSeq, fetchAgentMessagesAfterSeq, onWarning) so the test
exercises actual behavior instead of mocks, and update assertions to verify real
outcomes; additionally add one or more tests that call the public
startChannelBridgeWorker function (using the same real/test ChannelBridgeDeps)
to cover its lifecycle and observable side-effects (session resolution, message
fetching/sending, and warnings) rather than only testing internal helpers.
In `@apps/cli/src/channels/core/channelBridgeWorker.ts`:
- Around line 247-253: The handler currently trusts event.providerId from
inbound payload which can allow spoofing; normalize and override provider
identity with the adapter's trusted providerId before passing to command
handling and forwarding. Update the call to handleCommand in channelBridgeWorker
to pass event.payload/providerId (or any field named providerId) set to
adapter.providerId, and ensure any later forwarding logic in the same function
(the block around lines 257-279) also replaces uses of event.providerId with
adapter.providerId so routing/binding uses adapter.providerId throughout;
specifically adjust where handleCommand is invoked and where outbound send/bind
logic reads event.providerId.
- Around line 206-216: The current logic uses
deps.resolveLatestSessionSeq(...).catch(() => 0) which can persist a binding
with lastForwardedSeq=0 and replay full history; change the flow in the attach
path so you do not upsertBinding or confirm the attach when seq resolution
fails: call resolveLatestSessionSeq(resolved.sessionId) and if it throws or
returns an invalid cursor, send a failure reply via replyToConversation(adapter,
ref, ...) and return false (or abort) instead of calling store.upsertBinding;
only persist the binding and send the success message after a successful seq
resolution. Ensure you update the code paths around resolveLatestSessionSeq,
store.upsertBinding, and replyToConversation so the binding is only created when
a valid latestSeq is obtained.
- Around line 242-255: The current loop over params.adapters will abort the
whole tick if adapter.pullInboundMessages() or handleCommand() throws; wrap the
call to adapter.pullInboundMessages() in a try/catch so a failing adapter is
logged and the loop continues to the next adapter, and inside the inbound-events
loop wrap handleCommand(...) (and parseSlashCommand if you prefer) in a
try/catch so an error for one event is logged and skipped without stopping
processing of remaining events/adapters; reference the existing symbols:
params.adapters, adapter.pullInboundMessages(), parseSlashCommand(),
handleCommand({ command, event, adapter, store: params.store, deps: params.deps
}) and use the existing logger (e.g. params.deps.logger.error) or console.error
to record the adapter/event and error details.
---
Nitpick comments:
In `@apps/cli/src/channels/core/channelBridgeWorker.test.ts`:
- Around line 41-203: Add unit tests for the new public lifecycle API
startChannelBridgeWorker: write a test that calls startChannelBridgeWorker(...)
then calls trigger() once to ensure the worker runs its first tick (you can
assert by spying on executeChannelBridgeTick or deps called), a test that calls
stop() twice to verify idempotency, and a test that ensures adapter.stop() is
called when stop() is invoked (spy on the adapter harness' stop method). Use the
existing createAdapterHarness(), in-memory store, and deps mocks from the file
to locate where to hook spies and assertions; reference
startChannelBridgeWorker, trigger, stop, and adapter.stop to find the right
symbols to exercise and assert.
In `@apps/cli/src/channels/core/channelBridgeWorker.ts`:
- Around line 1-368: Add a file-level JSDoc header describing the module
responsibility (bridging channel adapters to sessions, command handling, and
agent->channel cursor semantics) and add JSDoc comments for each public exported
type/function: ChannelBridgeAdapter (describe
pullInboundMessages/sendMessage/optional stop contract and expected error
handling), ChannelBindingStore and ChannelSessionBinding (explain
lastForwardedSeq meaning and that updateLastForwardedSeq stores the highest seq
forwarded), ResolveSessionIdResult, createInMemoryChannelBindingStore (note
now() usage and normalization of seqs), executeChannelBridgeTick (document flow:
inbound pull -> commands -> forward user messages -> fetchAgentMessagesAfterSeq
where afterSeq is exclusive and returned messages should have seq > afterSeq),
and startChannelBridgeWorker (document tickMs bounds, SingleFlightIntervalLoop
usage and trigger/stop behavior); ensure comments mention expected behavior of
deps methods (resolveLatestSessionSeq, fetchAgentMessagesAfterSeq) and
error/warning callbacks so integrators know contracts and cursor semantics.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 135e570b-02cd-4832-9f9d-772aa7c7e57f
📒 Files selected for processing (2)
apps/cli/src/channels/core/channelBridgeWorker.test.tsapps/cli/src/channels/core/channelBridgeWorker.ts
|
@coderabbitai please re-review this PR now that all bot threads have explicit resolution notes and follow-up hardening is complete. @greptile-apps please run a fresh pass as well. |
|
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/cli/src/channels/core/channelBridgeWorker.ts`:
- Around line 565-570: The current Promise.all over params.adapters can abort if
any adapter.stop() throws; update the shutdown mapping in channelBridgeWorker.ts
to wrap each adapter.stop() call in a try/catch so every adapter.stop() is
attempted even if one fails, log the specific error (including adapter
identifier if available) inside the catch, and return a resolved value so
Promise.all waits for all wrappered stops to complete rather than rejecting on
the first error.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 05bcf020-8e3c-440d-9b84-d2c5072a6b1c
📒 Files selected for processing (2)
apps/cli/src/channels/core/channelBridgeWorker.test.tsapps/cli/src/channels/core/channelBridgeWorker.ts
|
Follow-up clarity pass — here are exact fix locations for the resolved bot items (commit
I added this map so reviewers can verify each previously-resolved thread against concrete code anchors quickly. |
|
@coderabbitai @greptile-apps follow-up fixes were pushed (commit 3a5d30a) for the latest unresolved threads; please re-review. |
|
✅ Actions performedFull review triggered. |
|
@coderabbitai @greptile-apps re-review |
|
✅ Actions performedFull review triggered. |
|
@greptile review |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/cli/src/channels/core/channelBridgeWorker.test.ts`:
- Around line 700-711: Wrap each test that starts a channel worker with a
try/finally so worker.stop() always runs: for tests that call
startChannelBridgeWorker(...) and then await waitFor(...) / assert on
harness.sent, move assertions into the try block and call await worker.stop() in
the finally block to ensure timers and in-flight work are cleaned up (apply the
same pattern to other tests that startChannelBridgeWorker, reference the worker
variable and its stop() method in the finally).
- Around line 67-73: The test's dedupe verification is passing prematurely
because after calling worker.trigger() the first tick already makes
sentToSession.length === 1 true; update the assertions in createAdapterHarness
tests (and the other occurrences at the noted sites) to deterministically wait
for the second tick before asserting dedupe behavior: after the first
worker.trigger() await the next processing cycle (e.g., call worker.trigger() a
second time or await a Promise that resolves when sentToSession.length >= 2) and
then assert that the duplicate message was ignored (sentToSession length remains
1) or that a dedupe-specific indicator was emitted; reference the harness
functions createAdapterHarness, pushInbound, sent (or sentToSession) and
worker.trigger() when updating each failing assertion to ensure the test
verifies the second tick outcome.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b7334bcb-a690-4b9f-8698-5f4f931f4799
📒 Files selected for processing (2)
apps/cli/src/channels/core/channelBridgeWorker.test.tsapps/cli/src/channels/core/channelBridgeWorker.ts
✅ Files skipped from review due to trivial changes (1)
- apps/cli/src/channels/core/channelBridgeWorker.ts
…back (cherry picked from commit 51bb9ea)
|
Addressed remaining feedback in commit 7fb5b65 (test determinism + lifecycle cleanup guards in channel bridge worker tests). @coderabbitai review |
|
✅ Actions performedFull review triggered. |
|
✅ Actions performedFull review triggered. |
|
@greptile review |
|
Addressed latest CodeRabbit nitpick in 4771584:
Validated with:
@coderabbitai review |
|
@greptile review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
|
@coderabbitai review |
|
@greptile review |
✅ Actions performedReview triggered.
|
|
Follow-up fix in 4d2649a:
RUN v3.2.4 /Users/cdenneen/code/workspace/work/happier-dev/apps/cli ✓ src/channels/core/channelBridgeWorker.test.ts (49 tests) 52ms Test Files 1 passed (1) Done in 6.31s. @coderabbitai review |
|
@greptile review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
|
@coderabbitai review |
|
@greptile review |
|
@coderabbitai review |
|
@greptile review |
✅ Actions performedReview triggered.
|
|
Superseded by #114 (clean rebase + squash replacement). Keeping this PR for history/context only. |
|
Closing as superseded by #114 (clean replacement PR). |
Summary
This PR introduces a provider-agnostic channel bridge core for bi-directional session messaging.
It adds the reusable bridge worker and adapter contract, but intentionally does not include any provider-
specific adapter wiring (Telegram/Discord/etc). This keeps the core isolated and reviewable.
What’s Included
apps/cli/src/channels/core/channelBridgeWorker.tspullInboundMessages,sendMessage, optionalstop)/sessions/attach <session-id-or-prefix>/session/detachlastForwardedSeq)Tests
apps/cli/src/channels/core/channelBridgeWorker.test.tsOut of Scope (Intentional)
These are added in the follow-up stacked PR.
Summary by CodeRabbit
New Features
Tests
Documentation