Skip to content

fix(webchat): preserve coalesced replies and retry failed turns - #6

Merged
BegoniaHe merged 4 commits into
masterfrom
fix/webchat-turn-handling
Sep 2, 2026
Merged

fix(webchat): preserve coalesced replies and retry failed turns#6
BegoniaHe merged 4 commits into
masterfrom
fix/webchat-turn-handling

Conversation

@BegoniaHe

@BegoniaHe BegoniaHe commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

WebChat coalesced fragments were finishing the pipeline with an empty completion (end) before the later flush event replied. Flush also reconstructed events through the four-argument constructor, so WebChatMessageEvent lost its queue manager and attachments directory.

Failed turns that wrote platform history but never a conversation checkpoint also could not be regenerated, edited, or used as a thread parent.

This keeps fragment events from emitting empty completion, clones the last event on flush, and lets regenerate/edit/thread continue from platform history when the conversation checkpoint is missing.

Rebased onto master after #7. No shared files remain.

Related issue

Related: #7

Root cause

  • PipelineScheduler.execute always sent empty completion for requires_empty_completion after stages. TurnCoalesceStage stops the fragment and defers work to a later flush, but the stopped fragment still emitted WebChat end.
  • _build_flush_event called last.__class__(message_str, message_obj, platform_meta, session_id). WebChatMessageEvent.__init__ also requires webchat_queue_manager and attachments_dir, so the flush event could not emit replies.
  • ChatService regenerate, edit, and thread creation required find_turn_range to match a conversation checkpoint. A failed AI turn can have platform history plus llm_checkpoint_id without a matching conversation-history range.

Reproduction

  1. Enable turn coalescing and send rapid consecutive WebChat messages. The first fragment emits end before the flush reply.
  2. Fail a WebChat LLM turn, then regenerate, edit, or create a thread from that bot message. The API returns Linked checkpoint not found.

Implementation notes

  • Mark coalesced fragments with event extra skip_empty_completion and honor it in the scheduler.
  • Build the flush event with copy.copy(last) so adapter-specific fields stay attached, then reset per-run extras, result, and send flags.
  • When turn_range is missing, skip conversation-history rewrite and continue from platform history for regenerate, edit, and thread creation. Older-turn branching checks still apply when a checkpoint range exists.
  • Record the user-visible fixes in changelogs/v4.27.6.md.

Validation

uv run ruff format --check astrbot/core/pipeline/scheduler.py astrbot/core/pipeline/turn_coalesce/stage.py astrbot/core/pipeline/turn_window.py astrbot/dashboard/services/chat_service.py tests/unit/test_chat_service.py tests/unit/test_pipeline_scheduler_order.py tests/unit/test_turn_coalesce.py
uv run ruff check astrbot/core/pipeline/scheduler.py astrbot/core/pipeline/turn_coalesce/stage.py astrbot/core/pipeline/turn_window.py astrbot/dashboard/services/chat_service.py tests/unit/test_chat_service.py tests/unit/test_pipeline_scheduler_order.py tests/unit/test_turn_coalesce.py
uv run pytest tests/unit/test_pipeline_scheduler_order.py tests/unit/test_turn_coalesce.py tests/unit/test_chat_service.py tests/unit/test_delivery_receipt.py -q

79 passed after rebase onto master. Did not run make check or the full pytest suite.

Compatibility and risk

No OpenAPI, route, or public plugin-API change. skip_empty_completion is an event-local extra. Residual risk: other adapters that depended on constructor-only flush reconstruction now keep extra instance fields via copy.copy; WebChat is the adapter that required that. Failed-turn recovery does not rewrite conversation history when no checkpoint range exists. Older-turn branching checks are skipped when turn_range is missing.

Checklist

  • The change is focused and does not include unrelated refactoring.
  • I added or updated a regression test, or explained why a test is not practical.
  • I ran the relevant formatting, lint, build, and test commands.
  • User-visible behavior updates both docs/zh/ and docs/en/ when needed.
  • OpenAPI, generated client, docs/public/openapi.json, and tests change together when routes or schemas change.
  • No secrets committed. Runtime Python deps update pyproject.toml, requirements.txt, and uv.lock together.
  • I did not restore legacy shims, Python <3.14 fallbacks, or upstream publish/docs URLs as fork artifacts.
  • Breaking API or behavior changes use ! and a BREAKING CHANGE: footer.
  • I will not merge this PR myself. Merge needs a human maintainer review plus a separate AI-assisted review (AI_POLICY.md).
  • AI use follows AI_POLICY.md. Keep exactly one author note below. Do not fabricate the other.

Agent note

Goal: rebase #6 onto master after #7 so GitHub merge is clean, and record the WebChat fixes in changelogs/v4.27.6.md.

Paths: astrbot/core/pipeline/scheduler.py, turn_coalesce/stage.py, turn_window.py, astrbot/dashboard/services/chat_service.py, unit tests, changelogs/v4.27.6.md.

Checks run: Ruff format/check on the seven product files; uv run pytest on scheduler/coalesce/chat_service/delivery_receipt, 79 passed. Residual risk: full make check / make test not run here; failed-turn paths without a conversation checkpoint leave conversation history untouched.

Tools: grok-4.6 in opencode.

@BegoniaHe

Copy link
Copy Markdown
Collaborator Author

Split from #7

#7 dropped the two WebChat commits that duplicated this branch (skip empty completion and regenerate without checkpoint). Those patches were byte-identical; this PR still has the extra edit/thread follow-up e42855171.

This PR remains the sole owner of WebChat turn-handling. Merge with #7 in either order; no shared files remain.

I will not merge.

Clone the last event on flush so WebChat retains queue-manager state,
and skip empty completion on buffered fragments so WebChat does not
emit end before the flush replies.

AI-Generated: true
Generated-At: 2026-09-01T22:01:31Z
Retry a failed WebChat turn from platform history when the conversation
checkpoint is missing, instead of rejecting the request.

AI-Generated: true
Generated-At: 2026-09-01T22:01:31Z
Keep editing a failed WebChat turn and creating a thread when the
conversation checkpoint is missing, instead of rejecting the request.

AI-Generated: true
Generated-At: 2026-09-01T22:05:12Z
Add the fork WebChat turn-handling fixes to the 4.27.6 notes after
rebasing onto master.

AI-Generated: true
Generated-At: 2026-09-02T18:24:37Z
@BegoniaHe
BegoniaHe force-pushed the fix/webchat-turn-handling branch from e428551 to cc0a161 Compare September 2, 2026 18:24

@BegoniaHe BegoniaHe left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AI-assisted review (not a maintainer approval)

Separate from the authoring Agent note. GitHub rejects REQUEST_CHANGES on a self-authored PR, so this is a comment review. I will not merge.

Reviewed head cc0a16183 after rebase onto master (9a73e897e, #7). File overlap with the absorbed sync is gone. Focused pytest 79 passed in this session.

Verdict

Technically mergeable once CI on this head is green and a human maintainer approves. No blocking product defect found.

What looks correct

  • Coalesced fragments set skip_empty_completion and stop_event(). Scheduler skips send(None), so WebChat does not emit end before flush.
  • Flush uses copy.copy(last) then resets _extras, result, send flags, and temp-file/task sets. WebChatMessageEvent keeps webchat_queue_manager / attachments_dir. Flush does not inherit the skip extra.
  • #9653 empty Respond skip is a different path (empty non-streaming chains). Fragments never reach Respond; flush carries merged content. They compose.
  • Regenerating/editing/threading a failed turn without find_turn_range continues from platform history. When a range exists, older-turn branching checks still run.
  • Tests cover skip-empty-completion, WebChat flush clone, and the three missing-checkpoint recovery paths.

Residual (non-blocking)

  • copy.copy is shallow. WebChat needed the extra instance fields; other adapters now keep extra fields too.
  • Missing turn_range skips conversation-history rewrite and older-turn checks. Intended for the last failed turn. An older failed bot message in a session that later continued can regenerate/edit/thread without the latest-turn guard.
  • Changelog records the user-visible fix. WebUI docs already mention regenerate; no API/docs contract change.

Human maintainer still has to approve. This review does not authorize merge.

@BegoniaHe
BegoniaHe merged commit 77ff9c8 into master Sep 2, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant