Skip to content

Zulip: add channel plugin#9643

Closed
jamie-dit wants to merge 18 commits intoopenclaw:mainfrom
jamie-dit:feat/zulip-channel
Closed

Zulip: add channel plugin#9643
jamie-dit wants to merge 18 commits intoopenclaw:mainfrom
jamie-dit:feat/zulip-channel

Conversation

@jamie-dit
Copy link
Copy Markdown

@jamie-dit jamie-dit commented Feb 5, 2026

Adds Zulip as a channel plugin (streams/topics) under extensions/zulip.

Key behavior:

  • Monitors a configured stream allowlist and replies in the same stream/topic.
  • Default outbound topic is general chat when a target omits the topic.
  • Topics map to OpenClaw sessions (same stream + same topic → same session).
  • Reaction indicators on the triggering message while responding (configurable):
    • Start: eyes
    • Success: check
    • Failure: warning

Config/targets:

  • Config lives under channels.zulip (supports accounts.* like other plugins).
  • Outbound target format: stream:<streamName>#<topic?>.

Scope / limitations:

  • MVP: stream messages only (no DMs; no attachments).

Implementation notes:

  • Adds resolveZulipSession for outbound session keys and treats zulip as markdown-capable.

Tests:

  • pnpm build
  • pnpm check
  • pnpm exec vitest run extensions/zulip/src/channel.test.ts src/infra/outbound/outbound-session.test.ts

Greptile Overview

Greptile Summary

This PR adds a new Zulip channel plugin under extensions/zulip that monitors an allowlisted set of streams, maps stream+topic to OpenClaw sessions, supports outbound targets like stream:<stream>#<topic?>, and optionally adds reaction indicators (start/success/failure) to the triggering message. It also updates core outbound session routing to generate stable Zulip session keys (including topic hashing for long topics) and marks zulip as markdown-capable.

Confidence Score: 3/5

  • This PR is mostly safe to merge, but has a couple behavior mismatches that should be fixed first.
  • Core architecture and tests look reasonable, but (1) the onboarding/env-var UX doesn’t match actual precedence rules and (2) multi-stream monitoring doesn’t actually constrain the event queue to the allowlist, which can materially change runtime behavior/perf on busy Zulip bots.
  • extensions/zulip/src/zulip/monitor.ts, extensions/zulip/src/zulip/accounts.ts

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Feb 5, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +121 to +123

const baseUrl = (configUrl || envUrl)?.replace(/\/+$/, "") || undefined;
const email = configEmail || envEmail || undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Env vars ignored by config. resolveZulipAccount currently prefers config values over env (baseUrl = (configUrl || envUrl) etc.). In onboarding (extensions/zulip/src/onboarding.ts:684-705) you prompt to “use env vars?” when they’re detected, but choosing “yes” doesn’t actually make env take precedence if config already has values; it just leaves config as-is. This makes the prompt misleading and can’t achieve “use env vars” unless the user clears config manually.

If the intent is to allow env to override config when the user opts in, you’ll need a config flag (or similar) that flips precedence to env || config for the default account, or otherwise ensure the onboarding path clears the stored fields when env is selected.

Also appears in: extensions/zulip/src/channel.ts:358-369 (describeAccount sources are tied to the same precedence).

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zulip/src/zulip/accounts.ts
Line: 121:123

Comment:
**Env vars ignored by config.** `resolveZulipAccount` currently prefers config values over env (`baseUrl = (configUrl || envUrl)` etc.). In onboarding (`extensions/zulip/src/onboarding.ts:684-705`) you prompt to “use env vars?” when they’re detected, but choosing “yes” doesn’t actually make env take precedence if config already has values; it just leaves config as-is. This makes the prompt misleading and can’t achieve “use env vars” unless the user clears config manually.

If the intent is to allow env to override config when the user opts in, you’ll need a config flag (or similar) that flips precedence to `env || config` for the default account, or otherwise ensure the onboarding path clears the stored fields when env is selected.

Also appears in: `extensions/zulip/src/channel.ts:358-369` (describeAccount sources are tied to the same precedence).

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 5, 2026

Additional Comments (1)

extensions/zulip/src/zulip/monitor.ts
Allowlist not enforced for multi-stream. When more than one stream is configured, registerQueue does not pass a narrow, so Zulip will deliver all message events for the bot’s subscriptions. shouldIgnoreMessage then filters in-process, but this means the plugin still ingests and processes (polls/parses) every subscribed stream, which violates the stated “monitors a configured stream allowlist” behavior and can be a big performance hit on busy bots.

If Zulip’s API supports multiple narrows (e.g. [["channel","A"],["channel","B"]] semantics) or registering separate queues per stream, the queue registration should be updated to actually constrain events to the allowlist when streams.length > 1.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zulip/src/zulip/monitor.ts
Line: 1278:1284

Comment:
**Allowlist not enforced for multi-stream.** When more than one stream is configured, `registerQueue` does not pass a `narrow`, so Zulip will deliver *all* message events for the bot’s subscriptions. `shouldIgnoreMessage` then filters in-process, but this means the plugin still ingests and processes (polls/parses) every subscribed stream, which violates the stated “monitors a configured stream allowlist” behavior and can be a big performance hit on busy bots.

If Zulip’s API supports multiple narrows (e.g. `[["channel","A"],["channel","B"]]` semantics) or registering separate queues per stream, the queue registration should be updated to actually constrain events to the allowlist when `streams.length > 1`.

How can I resolve this? If you propose a fix, please make it concise.

@openclaw-barnacle openclaw-barnacle bot added the commands Command implementations label Feb 5, 2026
@jamie-dit
Copy link
Copy Markdown
Author

Addressed Greptile feedback re multi-stream allowlist enforcement.

  • Zulip monitor now registers one event queue per configured stream (each with a channel:<stream> narrow), instead of registering an un-narrowed queue when multiple streams are configured.
  • Keeps the in-process shouldIgnoreMessage filter as a safety net.

Commit: fc1821e

@jamie-dit
Copy link
Copy Markdown
Author

Follow-up: reduce missed replies under rate limiting.

  • zulipRequest now tags errors with status and retryAfterMs (Retry-After header when present).
  • Outbound sendZulipStreamMessage now retries on 429/502/503/504 with exponential backoff (prefers Retry-After).

Commit: 75d4096

@jamie-dit
Copy link
Copy Markdown
Author

Fix: reaction cleanup (eyes spam)

Zulip reaction removal now uses query params instead of a DELETE request body, which is more reliable across Zulip deployments and should prevent the onStart "eyes" reaction from sticking around.

Commit: 25c0547

@jamie-dit
Copy link
Copy Markdown
Author

Fix: reduce Zulip /events 429s

  • Explicitly set dont_block=false on /events.
  • Add a small sleep when Zulip returns immediately with no events, to avoid a tight poll loop if long-polling isn't honored.

Commit: cb8c651

@jamie-dit
Copy link
Copy Markdown
Author

Fix: larger backoff on Zulip 429s

Increase the monitor backoff base for 429 responses (start at 10s, exponential from there) to avoid getting stuck in a 3s retry loop on stricter Zulip deployments.

Commit: 806dd29

@jamie-dit
Copy link
Copy Markdown
Author

Fix: throttle heartbeat-only Zulip events

Some Zulip deployments can return non-message events (e.g. heartbeat) even when event_types is restricted. Treating those as "empty" avoids an immediate re-poll loop that can contribute to 429s.

Commit: fb4775e

@jamie-dit
Copy link
Copy Markdown
Author

Fix: retry reaction add/remove on 429

Even with the DELETE query-param fix, reactions can still stick if Zulip rate-limits reaction endpoints (429). Reactions now use zulipRequestWithRetry (2 retries) so 👀 is much less likely to get stuck.

Commit: 2f32e01

@jamie-dit
Copy link
Copy Markdown
Author

Feature: option to leave 👀 (onStart) reaction

Add channels.zulip.reactions.clearOnFinish (default true). Set it to false to leave the onStart reaction (e.g. 👀) on the message after the bot responds.

Commit: f2b2806

@jamie-dit
Copy link
Copy Markdown
Author

Update: uploads + topic directive

  • Inbound uploads: detect Zulip user_uploads links, download up to channels.zulip.mediaMaxMb (default 5MB), and attach as MediaPaths/MediaUrls so the agent can see/process the files.
  • Outbound uploads: sendMedia now uploads via /api/v1/user_uploads and posts the resulting link into the stream/topic.
  • Topic creation: agent can switch topics by prefixing a reply with [[zulip_topic: <topic>]] (directive is stripped before posting).
  • Compatibility: register queues using the canonical stream narrow operator.
  • Docs: docs/channels/zulip.md updated with uploads + topic directive + reactions.clearOnFinish.

Commit: 0ac0da0

@rafaelreis-r
Copy link
Copy Markdown
Contributor

Duplicate #8365

FtlC-ian added a commit to FtlC-ian/openclaw that referenced this pull request Feb 8, 2026
Adds a full Zulip channel plugin with:
- Event queue polling with exponential backoff
- Authenticated upload downloads from /user_uploads/
- Outbound file uploads via /api/v1/user_uploads
- Reaction indicators (eyes → check_mark/warning)
- Topic directive [[zulip_topic: <topic>]]
- HTTP retry with backoff (429/502/503/504)
- Full actions API (stream CRUD, user management, reactions)
- Channel docs and unit tests

Refs: #5163, openclaw#8365, openclaw#9643
simpliq-marvin pushed a commit to simpliq-marvin/openclaw that referenced this pull request Feb 10, 2026
Adds a full Zulip channel plugin with:
- Event queue polling with exponential backoff
- Authenticated upload downloads from /user_uploads/
- Outbound file uploads via /api/v1/user_uploads
- Reaction indicators (eyes → check_mark/warning)
- Topic directive [[zulip_topic: <topic>]]
- HTTP retry with backoff (429/502/503/504)
- Full actions API (stream CRUD, user management, reactions)
- Channel docs and unit tests

Refs: #5163, openclaw#8365, openclaw#9643
FtlC-ian added a commit to FtlC-ian/openclaw that referenced this pull request Feb 11, 2026
Adds a full Zulip channel plugin with:
- Event queue polling with exponential backoff
- Authenticated upload downloads from /user_uploads/
- Outbound file uploads via /api/v1/user_uploads
- Reaction indicators (eyes → check_mark/warning)
- Topic directive [[zulip_topic: <topic>]]
- HTTP retry with backoff (429/502/503/504)
- Full actions API (stream CRUD, user management, reactions)
- Channel docs and unit tests

Refs: #5163, openclaw#8365, openclaw#9643
FtlC-ian added a commit to FtlC-ian/openclaw that referenced this pull request Feb 12, 2026
Adds a full-featured Zulip channel plugin with:

Core Features:
- Event queue polling with exponential backoff and auto-recovery
- Authenticated upload downloads from /user_uploads/
- Outbound file uploads via /api/v1/user_uploads
- Reaction indicators (eyes → check_mark/warning)
- Topic directive [[zulip_topic: <topic>]]
- HTTP retry with backoff (429/502/503/504)
- Full actions API (stream CRUD, user management, reactions)
- Channel docs and unit tests

Key Improvement Over Existing PRs:
- Implements CONCURRENT message processing with staggered start times
- Other PRs (openclaw#9643, openclaw#14182) process messages sequentially, causing
  reply delays and "message dumps" when multiple messages arrive
- This implementation starts processing each message immediately with
  a 200ms stagger for natural conversation flow
- Replies arrive as each finishes instead of all at once

Technical Details:
- Fire-and-forget message processing with per-message error handling
- Maintains event cursor and queue ID correctly during concurrent processing
- Tested live and confirmed significant UX improvement

Refs: openclaw#5163, openclaw#8365, openclaw#9643, openclaw#12183
@jamie-dit
Copy link
Copy Markdown
Author

New PR created to keep commits clean

feat: add Zulip channel plugin #15887

@jamie-dit jamie-dit closed this Feb 14, 2026
simpliq-marvin pushed a commit to simpliq-marvin/openclaw that referenced this pull request Feb 26, 2026
Adds a full-featured Zulip channel plugin with:

Core Features:
- Event queue polling with exponential backoff and auto-recovery
- Authenticated upload downloads from /user_uploads/
- Outbound file uploads via /api/v1/user_uploads
- Reaction indicators (eyes → check_mark/warning)
- Topic directive [[zulip_topic: <topic>]]
- HTTP retry with backoff (429/502/503/504)
- Full actions API (stream CRUD, user management, reactions)
- Channel docs and unit tests

Key Improvement Over Existing PRs:
- Implements CONCURRENT message processing with staggered start times
- Other PRs (openclaw#9643, openclaw#14182) process messages sequentially, causing
  reply delays and "message dumps" when multiple messages arrive
- This implementation starts processing each message immediately with
  a 200ms stagger for natural conversation flow
- Replies arrive as each finishes instead of all at once

Technical Details:
- Fire-and-forget message processing with per-message error handling
- Maintains event cursor and queue ID correctly during concurrent processing
- Tested live and confirmed significant UX improvement

Refs: openclaw#5163, openclaw#8365, openclaw#9643, openclaw#12183
FtlC-ian added a commit to FtlC-ian/openclaw that referenced this pull request Feb 27, 2026
Adds a full-featured Zulip channel plugin with:

Core Features:
- Event queue polling with exponential backoff and auto-recovery
- Authenticated upload downloads from /user_uploads/
- Outbound file uploads via /api/v1/user_uploads
- Reaction indicators (eyes → check_mark/warning)
- Topic directive [[zulip_topic: <topic>]]
- HTTP retry with backoff (429/502/503/504)
- Full actions API (stream CRUD, user management, reactions)
- Channel docs and unit tests

Key Improvement Over Existing PRs:
- Implements CONCURRENT message processing with staggered start times
- Other PRs (openclaw#9643, openclaw#14182) process messages sequentially, causing
  reply delays and "message dumps" when multiple messages arrive
- This implementation starts processing each message immediately with
  a 200ms stagger for natural conversation flow
- Replies arrive as each finishes instead of all at once

Technical Details:
- Fire-and-forget message processing with per-message error handling
- Maintains event cursor and queue ID correctly during concurrent processing
- Tested live and confirmed significant UX improvement

Refs: openclaw#5163, openclaw#8365, openclaw#9643, openclaw#12183
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands Command implementations docs Improvements or additions to documentation size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants