Fix Stream Chat scheduled delivery targets#9
Open
steookk wants to merge 4 commits into
Open
Conversation
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.
Summary
type:id) so scheduled/tool deliveries reuse the same channel identity.Why this fixes scheduled messages
The first working change was adding plugin-side target resolution for OpenClaw's message tool / cron outbound path. Normal inbound replies could work because they used the live reply pipeline, but scheduled/tool messages take a different route:
Before this change, OpenClaw asked the Stream Chat plugin whether a target such as
ai-test-channelbelonged to Stream Chat, but the plugin did not expose the newer messaging/outbound target resolver hooks that current OpenClaw expects. The delivery failed beforesendTextcould do useful work with errors like:This PR gives OpenClaw that adapter surface through
messaging.targetResolver,messaging.inferTargetChatType, andoutbound.resolveTarget. The simplified model now passes Stream's canonical raw CID, such asmessaging:ai-test-channel, through the gateway and scheduled job. When delivery fires, the plugin splits the CID intochannelTypeandchannelIdonly when calling the Stream SDK.Why this fixes streamed truncation (#6)
The old streamed-token issue still reproduced against
openclaw-user-1. In a real chat, the fourth response stopped atAdvantages - Reduceswhile the Stream Chat indicator cleared. Container logs showed Stream Chat rate limits on both partial and final message updates:That localizes the loss to the plugin-to-Stream delivery layer. OpenClaw had produced the response, but the final authoritative
partialUpdateMessage({ generating: false, text: fullText })was not retried. If Stream rejected that final update with 429, the visible message stayed at the last accepted partial update.This PR retries terminal message updates on retryable Stream Chat failures (
429,5xx, SDK code9, and the observedToo many requestserror). Partial streaming updates remain best-effort, but completion, error, empty-placeholder fallback, and force-stop terminal updates now get retry/backoff. The preview cadence is also reduced from chunks 1/3/5/7 plus every 15 chunks to chunks 1/5 plus every 35 chunks, keeping live typing while making long fast generations much less likely to exceed Stream's update-message rate limits. The generating indicator is sent fire-and-forget so a delayed indicator retry cannot enqueue a stalegenerating: truepartial update after finalization.Why this restores visible source streaming
After the scheduled-delivery work, user-1 chat-client showed only a final bot message. The cause was OpenClaw's group/channel default route falling into
message_tool_only, which made the model send the answer through the message tool as a separate final outbound message. That bypassed the Stream Chat placeholder andonPartialReplysource-reply path.This PR now forces
sourceReplyDeliveryMode: "automatic"for Stream Chat inbound replies. Scheduled/tool outbound delivery still uses the plugin target resolver andoutbound.sendText; ordinary inbound replies use the placeholder streaming path. The handler also de-duplicates cumulative partial snapshots against block/final dispatcher payloads, and can replace stale partial text if OpenClaw provides an authoritative non-prefix final payload.The explicit plugin reply option takes priority over
messages.groupChat.visibleRepliesbecause OpenClaw resolves an explicitly requestedsourceReplyDeliveryModebefore consulting config. That means the backend config change to generatevisibleReplies: "automatic"is still useful defensively for newly provisioned containers, older plugin code, other OpenClaw surfaces, and any Stream Chat path that does not pass explicit reply options. For this plugin's normal inbound reply path, however, placeholder streaming is treated as a plugin invariant, so the plugin pins automatic source replies directly.replaceText()is included for the rare case where OpenClaw's final dispatcher payload is not a prefix extension of the streamed partial text. Normally partials are cumulative snapshots and final text is identical. If OpenClaw or a provider later normalizes, filters, or repairs the final output differently from the preview stream, appending would createpartial + finalgarbage and ignoring the final would leave stale preview text. In that divergence case, the final payload is treated as authoritative and replaces the accumulated preview text before the final Stream Chat update.The duplicate-safe handling was not fixing an already-observed duplicate append in the old plugin behavior. Previously, text blocks from
deliver(...)were ignored becauseonPartialReplywas assumed to be the only visible text path. This PR opens a fallback path wheredeliver(...)text can contribute if partial streaming is absent or incomplete; the duplicate guard is what makes that fallback safe when normal cumulative partial streaming is active anddeliver(final)repeats the same text.Verification
npm run type-checknpx tsx scripts/test-streaming-final-retry.tsnpx tsx scripts/test-channel-streaming-delivery.tsmessaging:ai-test-channeldelivered successfully.ai-test-channelis rejected with the Stream CID hint.CID_SCHEDULED_FROM_CONTEXT_OK.openclaw cron list --jsonshowed no remaining jobs.