Skip to content

fix(mattermost): preserve text-block boundaries in draft preview (#87322)#87449

Merged
steipete merged 13 commits into
openclaw:mainfrom
yetval:fix/87322-mattermost-block-preview-boundaries
Jul 10, 2026
Merged

fix(mattermost): preserve text-block boundaries in draft preview (#87322)#87449
steipete merged 13 commits into
openclaw:mainfrom
yetval:fix/87322-mattermost-block-preview-boundaries

Conversation

@yetval

@yetval yetval commented May 27, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes #87322. Mattermost block previews could overwrite earlier assistant/tool blocks during one streamed turn, and streaming.mode: "off" with blockStreaming: true incorrectly suppressed normal block delivery.

Why This Change Was Made

  • Treat each visible assistant/tool activity as an ordered Mattermost preview generation, while keeping repeated tool updates on the current tool post.
  • Serialize callback starts for Mattermost so fire-and-forget assistant, reasoning, and tool events cannot overtake one another.
  • Preserve Responses item identity and phase metadata through OpenAI-compatible and proxy streams so commentary and final-answer boundaries stay stable.
  • Reconcile cumulative terminal output against only successfully published assistant previews, including response-prefix handling, so final delivery sends only the unpublished remainder.
  • Keep the existing streaming/config contract; no new config keys or runtime compatibility path.

User Impact

With Mattermost block streaming, earlier text and tool-progress blocks remain visible in source order instead of being overwritten. Reasoning placeholders remain transient, response prefixes appear once, long blocks retain configured chunking, and completed turns do not repeat preview text. Preview-off blockStreaming: true again produces ordinary separate block replies.

Evidence

Known proof gap: no approved authenticated Mattermost deployment was available for exact-head channel/UI validation. The focused runtime/HTTP and regression coverage exercises the delivery contract, but server-side rendering, notifications, and mobile push were not observed.

Fixes #87322

@openclaw-barnacle openclaw-barnacle Bot added channel: mattermost Channel integration: mattermost size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Codex review: stale review; fresh review needed.

Summary
The latest durable ClawSweeper review was for head b8f9f5f21f94454984a80fc37d5c9a3e605e6dd8, but the PR head is now e40a51294973a0123d7c68dd341a5d34db5957cf. Its old verdict and PR readiness labels are no longer current.

Next step
Run or wait for a fresh ClawSweeper review on the current PR head.

Review history (4 earlier review cycles)
  • reviewed 2026-06-29T14:33:06.767Z sha 7ce7fa0 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T23:25:10.289Z sha b052429 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T23:33:18.099Z sha b052429 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-10T11:57:46.744Z sha c3fe056 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 27, 2026
@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 🥚 common Velvet Merge Sprite

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 🥚 common.
Trait: sniffs out flaky tests.
Image traits: location branch lighthouse; accessory lint brush; palette plum, gold, and soft gray; mood calm; pose balancing on a branch marker; shell starlit enamel shell; lighting subtle sparkle highlights; background miniature CI buoys.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Velvet Merge Sprite in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 27, 2026
@yetval

yetval commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 27, 2026
@yetval
yetval force-pushed the fix/87322-mattermost-block-preview-boundaries branch from d969eef to 353f83b Compare May 27, 2026 23:59
@yetval

yetval commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Address P1 finding (draft-stream.ts:74-75): forceNewMessage is now async and awaits loop.flush() before clearing streamPostId; controller noteBoundary is async and the inbound callbacks (onAssistantMessageStart, onReasoningEnd, onToolStart) await it. New flushes a pending in-flight create before forcing a new post regression test holds the first POST /posts response and fires the boundary while the create is still in flight — asserts the next block lands as a separate POST (not a PUT). PR body Evidence section now shows a delayed-transport run (120ms POST hold, fastBoundary: true) where each boundary fires before the prior POST resolves; the timestamps show forceNewMessage() (awaiting pending flush)resolved ~120ms later → next POST. AFTER pass: 5 POST, 0 PUT. Head: 353f83b.

@clawsweeper

clawsweeper Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 28, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels May 28, 2026
@clawsweeper

clawsweeper Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval

yetval commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the boundary gate race P1 (draft-stream.ts:78). Head is now d6c9f85491.

Root cause: noteBoundary() left hasStreamedContent true while it awaited forceNewMessage(), then reset it to false after the await. Because the embedded subscriber dispatches both noteBoundary() and noteUpdate() fire-and-forget (src/agents/embedded-agent-subscribe.ts:281,1193, embedded-agent-subscribe.handlers.messages.ts:144,573,687), a noteUpdate() for the next block could land while a prior boundary was still awaiting; the older boundary then reset the flag to false, swallowing the streamed-content signal, so the following boundary skipped forceNewMessage() and the block after it edited into the previous post.

Fix: noteBoundary() now returns early when nothing streamed, then clears hasStreamedContent synchronously before awaiting forceNewMessage(), so a noteUpdate() during the await is preserved for the next boundary and no boundary is skipped.

Proof refreshed on current head, exercising the fire-and-forget path over live loopback HTTP (production stream + controller, real fetch round-trips, 127.0.0.1 only, placeholder token):

  • Gate clobber: block A then a fire-and-forget boundary, a noteUpdate() arriving while that boundary is pending, then a second boundary and block C. BEFORE (c8c90e0df8, gate unfixed): 2 posts, block C PUTs onto block B's post-2 and block B is lost. AFTER: 3 distinct posts (post-1, post-2, post-3).
  • Synchronous detach (prior P1): a fire-and-forget boundary with a partial arriving before it settles. BEFORE: PUT overwrites post-1. AFTER: new post-2, post-1 preserved.

New regression splits the next boundary when a noteUpdate arrives while the prior boundary is pending plus the synchronous-detach regression. Local: draft-stream 20/20, full extensions/mattermost/src/mattermost/ 29 files / 377/377, oxlint + oxfmt clean. Full before/after captures and reproduction details are in the updated PR body.

@clawsweeper

clawsweeper Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper

clawsweeper Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

@yetval

yetval commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the callback-ordering P1 at monitor.ts:1950. Head is now cfb5a1fd05.

The monitor's onAssistantMessageStart and onReasoningEnd awaited previewBoundaryController.noteBoundary() before resetting the per-turn local state (lastPartialText, progressDraft). Since the embedded subscriber dispatches these callbacks with void, the next block's onPartialReply could run during that await with lastPartialText still holding the previous block's text; updateDraftFromPartial then drops a new-block partial that is a prefix of the prior block as a stale repaint.

Fix: both callbacks now start the split, reset local state synchronously, then await (const boundarySettled = noteBoundary(); lastPartialText = ""; progressDraft.reset(); ...; await boundarySettled;).

Proof refreshed on the current head through the real subscribeEmbeddedAgentSession dispatch (no direct controller calls), redaction-safe (127.0.0.1, placeholder token). With block A's POST in flight and block B's only partial a prefix of block A: OLD ordering drops block B (1 post); NEW ordering preserves it (2 posts). Full before/after in the PR body, alongside the synchronous-detach and gate-clobber embedded-path captures.

Local: draft-stream 20/20, monitor.inbound-system-event 10/10, full extensions/mattermost/src/mattermost/ 29 files / 377/377, oxlint + oxfmt clean.

Remaining items I believe are maintainer decisions rather than code defects, and I have flagged them in the PR body for sign-off: (1) the intentional visible delivery-shape change (one edited post to multiple posts for block-mode and preview-off blockStreaming); (2) the additive exported DraftStreamLoop.takePending type; (3) a real self-hosted Mattermost UI capture, which I cannot produce from this environment (loopback + real embedded-callback proof is provided instead); (4) the separate error-response draft-deletion artifact noted on #87322, which is out of scope for this fix and looks like a follow-up.

@clawsweeper

clawsweeper Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval

yetval commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Re-requesting a clean review of the current head cfb5a1fd05. The durable verdict comment was just overwritten out of order: the cfb5a1fd05 review completed at 16:07, but a stale re-review of an earlier head (its evidence cites 56186cfe2512, before the synchronous-detach, gate, and monitor-ordering fixes) finished at 16:09 and clobbered it.

All three fire-and-forget races the prior verdicts flagged are fixed on cfb5a1fd05, and the detach is now synchronous through the whole chain:

  • Stream forceNewMessage bumps the generation and clears streamPostId synchronously before awaiting (the in-flight-create seal is preserved via a generation-keyed record).
  • Controller noteBoundary clears hasStreamedContent synchronously before awaiting forceNewMessage.
  • Monitor onAssistantMessageStart / onReasoningEnd start the split, reset lastPartialText / progressDraft synchronously, then await (monitor.ts:1949-1956). Because noteBoundary() runs its synchronous part (including forceNewMessage's synchronous detach) before any await, a fire-and-forget partial that arrives next deterministically opens a fresh post; it cannot flush against the old post.

Proof on the current head goes through the real subscribeEmbeddedAgentSession dispatch (genuine void callbacks, not direct controller calls), redaction-safe (127.0.0.1, placeholder token); before/after captures for all three races are in the PR body. Local: draft-stream 20/20, monitor.inbound-system-event 10/10, full extensions/mattermost/src/mattermost/ 377/377, oxlint + oxfmt clean.

The remaining items I believe are maintainer decisions rather than code defects (flagged in the PR body for sign-off): the intentional visible delivery-shape change, the additive exported DraftStreamLoop.takePending type, a real self-hosted Mattermost UI capture I cannot produce from this environment, and the separate error-response draft-deletion artifact on #87322 (follow-up).

@clawsweeper

clawsweeper Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval

yetval commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval

yetval commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@vincentkoc

Copy link
Copy Markdown
Member

Clownfish 🐠 reef update

Thanks for the contribution here. Clownfish gave this branch a little current boost so the original PR can stay the canonical swim lane instead of opening a replacement.

Source PR: #87449
Validation: pnpm check:changed
The useful context and credit stay on this branch. tidy swim, same contributor trail.

fish notes: model gpt-5.5, reasoning medium; reviewed against 55dcfbf.

@yetval

yetval commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@clawsweeper

clawsweeper Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@yetval

yetval commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-run

@steipete

Copy link
Copy Markdown
Contributor

Land-ready at exact head e40a51294973a0123d7c68dd341a5d34db5957cf.

Known gap: no approved authenticated Mattermost deployment was available for exact-head channel/UI validation. Source-blind behavior validation is blocked on the missing Mattermost URL/token and deployed runtime; server-side rendering, notifications, and mobile push were not observed.

Thanks @yetval for the original report-driven fix and persistence through review.

@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: mattermost Channel integration: mattermost channel: telegram Channel integration: telegram docs Improvements or additions to documentation P1 High-priority user-facing bug, regression, or broken workflow. proof: supplied External PR includes structured after-fix real behavior proof. size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mattermost: block streaming edits single post instead of creating separate messages per text block

4 participants