fix(notifications): restore completion sound speed scaling for local tasks - #3166
Conversation
…tasks The agent.prompt mutation resolves in parallel with the streamed JSON-RPC response and nulls promptStartedAt. Since the 16ms event batching landed (#3063), the mutation reliably wins, so the completion notification computed its duration from an already-cleared timestamp and the sound always played at 1x. Read the turn start from liveTurnContent (captured before prompt state is cleared) instead, with promptStartedAt as the reconnect fallback. Generated-By: PostHog Code Task-Id: 27accb09-ec92-4c97-9723-a886485fac80
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
|
Reviews (1): Last reviewed commit: "fix(notifications): restore completion s..." | Re-trigger Greptile |
| const turnStartedAtTs = | ||
| this.liveTurnContent.get(taskRunId)?.startedAtTs ?? | ||
| session.promptStartedAt; |
There was a problem hiding this comment.
Duplicated
turnStartedAtTs lookup
The expression this.liveTurnContent.get(taskRunId)?.startedAtTs ?? session.promptStartedAt is now identical at lines 1784–1786 (cloud isTurnCompleteEvent branch in updatePromptStateFromEvents) and here. Extracting it to a small private helper — e.g. getTurnStartedAtTs(taskRunId: string, session: AgentSession) — would satisfy the codebase's OnceAndOnlyOnce rule and make the intent self-documenting at both callsites.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
What
The "Scale sound speed with task length" setting stopped working for local tasks: every completion sound played at 1x regardless of turn duration. This restores the scaling.
Root cause
For local tasks, two parallel signals report turn completion:
agent.prompttRPC mutation resolves and immediately runsupdateSession({ isPromptPending: false, promptStartedAt: null }).notifyPromptComplete, computing the duration asacpMsg.ts - session.promptStartedAt.When the feature shipped (#3026), stream events were handled on arrival, so (2) read
promptStartedAtbefore (1) cleared it. #3063 then started buffering stream events on a 16ms flush timer — the mutation callback now reliably wins the race, so the response event always sawpromptStartedAt: null, passeddurationMs: undefined, and the notification bus fell back toplaybackRate = 1. The notification itself still fired (the mutation doesn't touchcurrentPromptId, so the notify guard passed), which matched the symptom exactly: sound plays, never scaled.How
handleSessionEventnow reads the turn start fromliveTurnContent— the per-turn tally keyed by the prompt echo's timestamp, which the mutation callback never touches — captured beforeupdatePromptStateFromEventsfinalizes it, withsession.promptStartedAtas the fallback for a mid-turn reconnect (echo replayed from history, so no tally, but also no in-flight mutation to race).The cloud
turn_completepath gets the same capture-before-clear treatment. It previously readsession.promptStartedAtafter theupdateSessionthat nulls it, working only because the Immer store is copy-on-write — the test harness's in-place-mutating fake store lost the value, which is why the existing cloud test asserteddurationMs === undefined(documenting broken behavior).Testing
sessionEventBatching.test.tsreproducing the exact race (prompt echo → mutation clears state → buffered response flushes) — verified failing without the fix, asserts the notification carriesdurationMs = 5000. The harness's fakeupdateSessionis now copy-on-write to match the real Immer store's semantics.cloudTaskUpdateNotifications.test.tsfixtures now carry explicit timestamps and the payload test asserts45_000instead ofundefined.@posthog/coresuite green (1926 tests), typecheck clean, biome clean.Created with PostHog Code