feat(zulip): streaming responses + steering via session injection #23

Merged
jerome merged 2 commits from feat/zulip-streaming into main 2026-06-24 20:42:07 +00:00
Owner

Problem

Zulip DMs were fully buffered — the user saw nothing until the entire LLM response was complete. No way to steer the conversation mid-response, unlike the terminal TUI where you can Ctrl+C and type a follow-up.

Solution

Three-part upgrade to the session-injection architecture:

1. Streaming visibility

  • On DM receipt, sends a 🤖 _Thinking..._ placeholder message to Zulip immediately
  • message_update event handler accumulates partial text and edits the Zulip message every ~2 seconds with the live content
  • On agent_end, the placeholder is edited one final time with the complete response

2. Steering (mid-response interruption)

  • turn_start/agent_end lifecycle tracks isAgentBusy state
  • When a new Zulip DM arrives while the LLM is streaming, pi.sendUserMessage() is called with { deliverAs: "steer" } — this queues the message and delivers it after the current turn finishes
  • Effectively the same as typing a follow-up in the terminal: the original turn completes, the steer message becomes the next input, and a new turn starts

3. API additions

  • sendMessage() now returns the Zulip message ID
  • Added editMessage(id, content) using PATCH /messages/{id}
  • Streaming timer properly cleaned up on shutdown

Usage

Send a Zulip DM to the bot → see "Thinking..." immediately → watch the response stream in live → send another DM mid-stream to interrupt and steer.

## Problem Zulip DMs were fully buffered — the user saw nothing until the entire LLM response was complete. No way to steer the conversation mid-response, unlike the terminal TUI where you can Ctrl+C and type a follow-up. ## Solution Three-part upgrade to the session-injection architecture: ### 1. Streaming visibility - On DM receipt, sends a `🤖 _Thinking..._` placeholder message to Zulip immediately - `message_update` event handler accumulates partial text and edits the Zulip message every ~2 seconds with the live content - On `agent_end`, the placeholder is edited one final time with the complete response ### 2. Steering (mid-response interruption) - `turn_start`/`agent_end` lifecycle tracks `isAgentBusy` state - When a new Zulip DM arrives while the LLM is streaming, `pi.sendUserMessage()` is called with `{ deliverAs: "steer" }` — this queues the message and delivers it after the current turn finishes - Effectively the same as typing a follow-up in the terminal: the original turn completes, the steer message becomes the next input, and a new turn starts ### 3. API additions - `sendMessage()` now returns the Zulip message ID - Added `editMessage(id, content)` using `PATCH /messages/{id}` - Streaming timer properly cleaned up on shutdown ## Usage Send a Zulip DM to the bot → see "Thinking..." immediately → watch the response stream in live → send another DM mid-stream to interrupt and steer.
abiba-bot added 1 commit 2026-06-24 20:31:04 +00:00
- Adds message_update handler that periodically edits the Zulip
  placeholder with partial response text (every ~2s during streaming)
- Sends a 'Thinking...' placeholder to Zulip immediately on DM receipt
- Detects agent busy state and uses deliverAs: 'steer' so subsequent
  Zulip DMs steer the conversation mid-stream (like terminal TUI)
- Adds editMessage() to the queue helper (Zulip PATCH /messages/{id})
- sendMessage() now returns the message ID for editing
- Turn_start / agent_end lifecycle tracks agent busy state
- Cleanup: streaming timer properly cleared on shutdown
abiba-bot added 1 commit 2026-06-24 20:33:58 +00:00
jerome merged commit 1d289a1328 into main 2026-06-24 20:42:07 +00:00
Sign in to join this conversation.