CL-6432: turn-phase typing indicator (reopened) - #183
Merged
Merged
Conversation
Rewrites the CL-6432 block to the event order a live folded run actually produces: message.run.started opens the turn, the reply streams and posts via connector.reply, then post-reply tool-only rounds (memory writes) run inference again and the run PARKS — no message.run.ended at that point. The old block's fabricated run-end event is gone; the post-reply rounds now assert the indicator stays down, and the next turn asserts it comes back.
StreamingReplyState carries an explicit phase. connector.reply moves the current turn to "replied" instead of clearing to idle, and in that phase inference.start / inference.done / reactor.start are inert — the post-reply memory rounds of a parked folded run can no longer re-open the pending pulse. The indicator returns only on a genuinely new turn: message.run.started from the stream, or the local send that opens one. Hard-terminal events (reactor.done/error, message.run.ended, inference.error, a turnFailed notice) still return to idle from any phase.
This was referenced Aug 21, 2026
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.
The typing dots stayed up after Myra's reply again. This replaces the
event-pair rule PR #159 shipped with a turn-phase state machine.
Why the first fix failed live
#159 cleared the indicator on
connector.replyandmessage.run.ended, and its unit test drove both. Live, the second eventnever arrives at that point: a folded run parks after the turn rather
than closing its bracket. What actually happens is
message.run.started— the harness dequeues the user's messageinference.start→inference.text.delta…)connector.reply— the orchestrator posts the persisted reply (CL-6432: clear the typing pulse on folded-run turn-terminal events #159cleared here, correctly)
inference.start, which re-opened the empty pending pulseinference.doneleft the pulse up, by thedeliberate "an empty pending survives a tool round" rule
message.run.ended, noreactor.done, nothingterminal ever arrives
So the indicator went back up right after the reply rendered and stayed
up until the 120s backstop. The unit test passed because it fed a run-end
event that a live parked run does not emit.
The fix
StreamingReplyStatenow carries an explicit phase:"awaiting"— a turn is in flight; empty text renders the pulse,streamed text renders the growing bubble (the old behaviour)
"replied"— this turn'sconnector.replyhas posted; rendersnothing, and every
inference.start/inference.done/reactor.startis inert, so post-reply tool rounds cannot re-open itnull— idleThe indicator comes back only on a genuinely new turn, keyed to a
real event rather than a timer:
message.run.startedfrom the stream(the harness's per-dequeued-message bracket open, the same event
chat-orchestrator.tskeys new turns off viamessageRunStarted), orthe local send that opens one (
openPendingReply). The hard-terminalevents —
reactor.done/reactor.error,message.run.ended,inference.error, and aturnFailednotice — still return to idle fromany phase, so no failure path is left stranded.
Tests
The CL-6432 describe block is rewritten to the real order above: no
fabricated
message.run.endedafter the reply. It walks reply → memoryrounds → park, asserting the indicator stays down through every
post-reply round, and asserts it returns on the next turn.
chat-ui (620) and folded-runs (71) suites pass; chat-ui typechecks clean.
Live verification: pending
Being straight about this, since it is exactly what reopened the ticket.
I booted a scratch stack (own DB, port 3042, Studio Ollama connected as
an
openai-compatibleprovider withqwen3.8:27b) and drove real turnsthrough the chat API, but could not get Myra off the seed-time
claude-sonnet-5pin on that stack — every turn came back as the"can't reach a model" notice rather than a real reply, so the
reply-then-memory-rounds sequence never ran there. The event order encoded
in the tests is the one the ticket documents from the owner's live repro,
not one I captured myself. A live confirmation on a stack with a working
provider is still owed.
Fixes CL-6432