fix(desktop): sync derived active-turn liveness in channel activity path#1492
Merged
Conversation
This was referenced Jul 3, 2026
tellaho
added a commit
that referenced
this pull request
Jul 6, 2026
Rebased from tho/active-turn-bridge-fix (#1492) onto post-#1380 main, squashing the temporary debug-harness add/remove churn that netted to zero. Behavior preserved: - ChannelScreen mounts useActiveAgentTurnsBridge next to the existing observer bridge so raw observer frames (e.g. turn_completed) and the derived liveness store stay in sync in the channel activity path - Extract syncActiveAgentTurnsFromObserver() from the bridge hook so the regression test drives the exact observer→derived-liveness path - resetActiveAgentTurnsStore() wired into workspace-switch reset per the workspace-switching singleton contract - Regression coverage: observer→active-turns bridge sync keeps the raw feed's completion event while derived liveness clears; non-running agents don't populate the store Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
f5edf42 to
f9ffb01
Compare
tellaho
added a commit
that referenced
this pull request
Jul 6, 2026
Rebased from tho/active-turn-bridge-fix (#1492) onto post-#1380 main, squashing the temporary debug-harness add/remove churn that netted to zero. Behavior preserved: - ChannelScreen mounts useActiveAgentTurnsBridge next to the existing observer bridge so raw observer frames (e.g. turn_completed) and the derived liveness store stay in sync in the channel activity path - Extract syncActiveAgentTurnsFromObserver() from the bridge hook so the regression test drives the exact observer→derived-liveness path - resetActiveAgentTurnsStore() wired into workspace-switch reset per the workspace-switching singleton contract - Regression coverage: observer→active-turns bridge sync keeps the raw feed's completion event while derived liveness clears; non-running agents don't populate the store Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
f9ffb01 to
22feb20
Compare
Rebased from tho/active-turn-bridge-fix (#1492) onto post-#1380 main, squashing the temporary debug-harness add/remove churn that netted to zero. Behavior preserved: - ChannelScreen mounts useActiveAgentTurnsBridge next to the existing observer bridge so raw observer frames (e.g. turn_completed) and the derived liveness store stay in sync in the channel activity path - Extract syncActiveAgentTurnsFromObserver() from the bridge hook so the regression test drives the exact observer→derived-liveness path - resetActiveAgentTurnsStore() wired into workspace-switch reset per the workspace-switching singleton contract - Regression coverage: observer→active-turns bridge sync keeps the raw feed's completion event while derived liveness clears; non-running agents don't populate the store Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
22feb20 to
8d0becf
Compare
wpfleger96
reviewed
Jul 6, 2026
| }, [messagesQuery.data]); | ||
| // No `lastMessageAt` fallback: it is reply-inclusive and would clear unread | ||
| // thread/sidebar state before a real top-level position is known. | ||
| const activeReadAt = latestActiveMessage |
Collaborator
There was a problem hiding this comment.
🟢 Nit: the comment removed just above this line was load-bearing context.
The deleted two lines explained why lastMessageAt is not used as an activeReadAt fallback — it's reply-inclusive and would clear unread thread/sidebar state before a real top-level position is known. The logic is unchanged and correct, but that constraint isn't obvious from the surrounding code. Suggest restoring the comment so the null fallback stays self-documenting.
Restores the comment explaining why lastMessageAt is not used as an activeReadAt fallback: it is reply-inclusive and would clear unread thread/sidebar state before a real top-level position is known. Behavior unchanged. ChannelScreen.tsx sat at 999/1000 lines, so the two restored comment lines trip the file-size guard; adds a narrowly scoped 1002-line override entry noting the comment-only overage. Addresses review feedback on #1492. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
wpfleger96
approved these changes
Jul 7, 2026
Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
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.
Overview
Category: fix
User Impact: The working/liveness indicator in a channel's agent Activity panel now clears as soon as the agent's turn completes, instead of continuing to spin after the work is done.
Problem: In the channel Activity panel, a raw
turn_completedobserver event could be visible in the feed while the liveness indicator kept spinning. The channel screen mounted the observer bridge (which feeds raw ACP frames into the activity panel) but never mounted the active-turns bridge, so the derived liveness store went stale — the exact "raw event arrived but the non-raw feed didn't update" mismatch. Nearby surfaces (profile panel, agents page, sidebar) already mount both bridges and were unaffected.Solution: Mount
useActiveAgentTurnsBridgeright next to the existing observer bridge inChannelScreen, on the same agent list, so channel-activity derived liveness consumes the same observer events the raw rail does. The bridge's sync loop is extracted into an exportedsyncActiveAgentTurnsFromObserverso a regression test can drive the real observer-ingestion path without a React renderer. Also wiresresetActiveAgentTurnsStore()into the workspace-switch reset, closing a singleton-leak gap per the workspace-switching contract in AGENTS.md.File changes
desktop/src/features/channels/ui/ChannelScreen.tsx
Mount
useActiveAgentTurnsBridge(observerBridgeAgents)alongside the existinguseManagedAgentObserverBridgeso the derived active-turns store stays in sync with raw observer frames in the channel activity path. The store dedupes per-agent via a watermark, so double-mounting with other surfaces is a no-op.desktop/src/features/agents/activeAgentTurnsStore.ts
Extract the bridge's sync loop into exported
syncActiveAgentTurnsFromObserver(agents); the hook now delegates to it. No behavior change — enables the regression to exercise the exact production path. AlsoresetActiveAgentTurnsStore()is now invoked on workspace switch.desktop/src/features/agents/activeAgentTurnsStore.test.mjs
New regression "observer → active-turns bridge sync": injects
turn_startedthenturn_completedthrough the real observer ingestion path and asserts the raw feed retains the completion event while derived liveness clears. Plus a case asserting non-running/deployed agents don't populate the store.desktop/src/features/workspaces/useWorkspaceInit.ts
Add
resetActiveAgentTurnsStore()toresetWorkspaceState()— the active-turns store is a module-level singleton holding workspace-scoped state and previously leaked across workspace switches.desktop/src/features/agents/ui/ManagedAgentSessionPanel.tsx
Net-zero change from the temporary debug harness used during diagnosis (added and fully removed within this branch); only minor prop-ordering churn remains.
Reproduction Steps
turn_completedappeared in the raw feed; now it clears promptly.cd desktop && pnpm test— see the "observer → active-turns bridge sync" regression inactiveAgentTurnsStore.test.mjs(1551/1551 pass).No visual/markup changes — this fixes the state plumbing the liveness UI depends on.