fix(kimi-web): single-source session status to stop duplicate turn-end notifications - #1542
Merged
Merged
Conversation
…d notifications The web client received two sessionStatusChanged events per turn transition: one projected client-side from the raw turn.started/turn.ended stream, one mapped from the daemon's event.session.status_changed. After the tag scheme in #1479 keyed the completion notification by prompt id, the second (redundant) idle event lost the cached prompt id and fell back to a Date.now() tag, so every turn end popped a second "Turn finished" notification and replayed the completion sound. Stop projecting sessionStatusChanged from the raw turn stream (turn.started, turn.ended, and the in-flight snapshot seed). The daemon's event.session.status_changed is the single source of status transitions: it is computed from live daemon state (covering awaiting-approval / awaiting-question / aborted), carries the authoritative previousStatus and currentPromptId, and is deduped per real transition server-side. The turn stream keeps its content responsibilities (message finalization, usage, duration); seedInFlight keeps seeding the partially-streamed message while status comes from the snapshot's authoritative session record.
🦋 Changeset detectedLatest commit: e77fd63 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
Merged
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.
Related Issue
No linked issue — reported by Windows web UI users: the "Turn finished" desktop notification pops twice for every completed turn.
Problem
Each turn end delivered two
sessionStatusChangedevents to the web client:turn.endedagent event, projected client-side intosessionStatusChanged(agentEventProjector), andevent.session.status_changed, mapped to the same AppEvent shape (mappers).onSessionIdleran for both. The first run consumed the cached prompt id and keyed the notification's dedup tag with it; the second found the cache empty and fell back to aDate.now()tag — a different tag, so the same-tag silent-replace never kicked in and a second "Turn finished" notification popped (the completion sound replayed too). The double event predates #1479, but was invisible while the tag was session-scoped; #1479's per-turn tags exposed it.What changed
Stop projecting
sessionStatusChangedfrom the raw turn stream, so the daemon'sevent.session.status_changedis the single source of session-status transitions:agentEventProjector: removed the status projection fromturn.started,turn.ended, and the in-flight snapshot seed (seedInFlightstill returns the seededmessageCreated; status comes from the snapshot's authoritative session record).turn.endedkeeps its content responsibilities — finalizing the assistant message (status +durationMs) and emittingsessionUsageUpdated.awaitingApproval/awaitingQuestion/abortedwith priority rules), carries the authoritativepreviousStatus/currentPromptId, is deduped per real transition server-side (previous === next), is durable/journaled (survives reconnect), and is globally broadcast so background sessions the client is not subscribed to still update. The projector's hardcodedpreviousStatus: 'running'could also disagree with the daemon on awaiting states — that divergence goes away too.client.ts(seedSnapshot) anduseWorkspaceState.ts(bindNextPromptIdno longer drives Stop; the submit response + daemon status event do).Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.