fix(agent): release activeRequests before publishing TypeAgentFinished

Sven Olsen created

I keep hitting a bug where a turn finishes -- desktop notification
fires, agent is clearly done -- but the terminal stays stuck on
the spinner with input blocked until something unrelated wakes
the TUI up.

The TUI polls IsSessionBusy() inside its TypeAgentFinished
handler and only re-evaluates on incoming tea.Msgs.  Run() right
now publishes the notification while activeRequests still holds
the session's cancel func, so the poll on receipt sees true and
the busy gates stay stuck.  Reorder so cleanup runs first.

💘 Generated with Crush

Assisted-by: Claude Opus 4.7 via Crush <crush@charm.land>

Change summary

internal/agent/agent.go | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)

Detailed changes

internal/agent/agent.go 🔗

@@ -581,16 +581,6 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
 		return nil, err
 	}
 
-	// Send notification that agent has finished its turn (skip for
-	// nested/non-interactive sessions).
-	if !call.NonInteractive && a.notify != nil {
-		a.notify.Publish(pubsub.CreatedEvent, notify.Notification{
-			SessionID:    call.SessionID,
-			SessionTitle: currentSession.Title,
-			Type:         notify.TypeAgentFinished,
-		})
-	}
-
 	if shouldSummarize {
 		a.activeRequests.Del(call.SessionID)
 		if summarizeErr := a.Summarize(genCtx, call.SessionID, call.ProviderOptions); summarizeErr != nil {
@@ -608,10 +598,23 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy
 		}
 	}
 
-	// Release active request before processing queued messages.
+	// Release active request before publishing the notification.
+	// TUI handlers poll IsSessionBusy() and only re-evaluate when a
+	// tea.Msg arrives, so the cleanup must precede the notify or
+	// subscribers see stale busy state at the moment of receipt.
 	a.activeRequests.Del(call.SessionID)
 	cancel()
 
+	// Send notification that agent has finished its turn (skip for
+	// nested/non-interactive sessions).
+	if !call.NonInteractive && a.notify != nil {
+		a.notify.Publish(pubsub.CreatedEvent, notify.Notification{
+			SessionID:    call.SessionID,
+			SessionTitle: currentSession.Title,
+			Type:         notify.TypeAgentFinished,
+		})
+	}
+
 	queuedMessages, ok := a.messageQueue.Get(call.SessionID)
 	if !ok || len(queuedMessages) == 0 {
 		return result, err