← Back to PRs

#19398: feat(signal): support native signal-cli JSON-RPC WebSocket

by jxstanford open 2026-02-17 19:14 View on GitHub →
channel: signal size: M
## Summary - Add auto-detection of signal-cli API mode (SSE vs JSON-RPC WebSocket) at startup - When `/api/v1/events` (SSE, used by bbernhard/signal-cli-rest-api) is unavailable, transparently fall back to the native signal-cli JSON-RPC WebSocket at `/api/v1/rpc` - JSON-RPC `receive` notifications are normalised into the existing `SignalSseEvent` shape, so the event handler works unchanged ## Motivation The signal channel currently requires the [bbernhard/signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) Docker wrapper, which adds an SSE endpoint (`/api/v1/events`) on top of signal-cli. The native signal-cli daemon (`signal-cli daemon --http`) doesn't expose SSE — it uses JSON-RPC over HTTP and WebSocket. This change eliminates the dependency on the third-party wrapper by supporting signal-cli's native protocol directly. The existing SSE path is preserved for users running the bbernhard wrapper. ## Changes | File | What | |------|------| | `src/signal/client.ts` | Add `detectSignalApiMode()` and `streamSignalJsonRpc()` | | `src/signal/sse-reconnect.ts` | Add `runSignalReceiveLoop()` (auto-detect) and `runSignalJsonRpcLoop()` | | `src/signal/monitor.ts` | Switch from `runSignalSseLoop` → `runSignalReceiveLoop` | | `src/signal/client.jsonrpc.test.ts` | New tests for detection and WebSocket streaming | | `src/signal/monitor.tool-result.test-harness.ts` | Add new exports to client mock | ## Test plan - [x] All 108 existing signal tests pass (12 test files) - [x] 7 new tests cover `detectSignalApiMode` and `streamSignalJsonRpc` - [ ] Manual: verify SSE mode still works with bbernhard wrapper - [ ] Manual: verify JSON-RPC WebSocket mode works with native signal-cli daemon <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds auto-detection of signal-cli API mode at startup, transparently falling back from the bbernhard SSE wrapper (`/api/v1/events`) to the native signal-cli JSON-RPC WebSocket (`/api/v1/rpc`) when SSE is unavailable. The implementation is well-structured: detection is a one-shot probe at startup, the JSON-RPC `receive` notifications are normalised to the existing `SignalSseEvent` shape so the event handler is untouched, and the reconnect loop mirrors the SSE loop pattern. The `ws` dependency and `@types/ws` are already in `package.json`. Key observations: - The `String(raw)` used to parse WebSocket messages should be `rawDataToString(raw)` from `src/infra/ws.ts`, consistent with every other WebSocket handler in the codebase (e.g. `src/gateway/client.ts`). `String()` fails silently on `ArrayBuffer` and `Buffer[]` variants of `WebSocket.RawData`. - `detectSignalApiMode` intentionally uses its own 3 s `AbortController` and does not accept the external abort signal; this is acceptable at startup but means a shutdown during detection will pause up to 3 s before the loop exits. - The `account` parameter is correctly omitted from `runSignalJsonRpcLoop` since the native signal-cli WebSocket is not filtered by account URL param (the daemon is typically run per-account). - Existing 108 signal tests remain unaffected: the test harness mocks `detectSignalApiMode` to return `"sse"`, preserving the old SSE-only code path in unit tests. <h3>Confidence Score: 4/5</h3> - Safe to merge with the rawDataToString style fix applied; no logic or security issues found. - The core logic is correct — URL transformation, abort signal lifecycle, timeout cleanup, and payload normalisation are all sound. The only actionable finding is using String(raw) instead of the codebase's rawDataToString utility for WebSocket message parsing, which is a robustness issue rather than a present bug (signal-cli sends text frames which arrive as Buffer, where String() works correctly). Test coverage is good for the new code paths. - src/signal/client.ts — the String(raw) → rawDataToString(raw) change in streamSignalJsonRpc's message handler. <sub>Last reviewed commit: d91da57</sub> <!-- greptile_other_comments_section --> <sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs