-
Notifications
You must be signed in to change notification settings - Fork 339
TUI: add FollowupBehavior setting to choose between steer and follow-up dispatch #2477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
020d8d1
d4feddf
31bdea1
4f823e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -351,6 +351,61 @@ func (m *appModel) handleToggleSplitDiff() (tea.Model, tea.Cmd) { | |
| return m, tea.Batch(cmds...) | ||
| } | ||
|
|
||
| // handleSetFollowupBehavior sets the follow-up behavior persistently. When | ||
| // called with an empty mode it reports the current value. Invalid modes | ||
| // produce a warning notification listing the accepted values. | ||
| func (m *appModel) handleSetFollowupBehavior(mode string) (tea.Model, tea.Cmd) { | ||
| mode = strings.ToLower(strings.TrimSpace(mode)) | ||
|
|
||
| current := userconfig.Get().GetFollowupBehavior() | ||
| if mode == "" { | ||
| return m, notification.InfoCmd(fmt.Sprintf( | ||
| "Follow-up behavior: %s · usage: /followup-behavior steer|followup", current)) | ||
| } | ||
|
|
||
| if mode != userconfig.FollowupBehaviorSteer && mode != userconfig.FollowupBehaviorFollowUp { | ||
| return m, notification.WarningCmd(fmt.Sprintf( | ||
| "Unknown follow-up behavior %q. Valid values: %s, %s", | ||
| mode, userconfig.FollowupBehaviorSteer, userconfig.FollowupBehaviorFollowUp)) | ||
| } | ||
|
|
||
| if mode == current { | ||
| return m, notification.InfoCmd(fmt.Sprintf("Follow-up behavior already set to %q", mode)) | ||
| } | ||
|
|
||
| // Apply the change in-memory synchronously so userconfig.Get returns the | ||
| // new value immediately. Without this the send-dispatch and | ||
| // working-indicator readers below would race the background file write | ||
| // and keep returning the old mode until it commits — meaning a message | ||
| // sent right after the command could be dispatched under the old mode. | ||
| userconfig.SetFollowupBehaviorOverride(mode) | ||
|
|
||
| // Persist to global userconfig (fire-and-forget, matching the split-diff pattern). | ||
| go func() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MEDIUM:
A user who types Fix: Apply the change to an in-memory field (e.g. update a package-level variable via a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 4f823e4. Added |
||
| cfg, err := userconfig.Load() | ||
| if err != nil { | ||
| slog.Warn("Failed to load userconfig for follow-up behavior change", "error", err) | ||
| return | ||
| } | ||
| if cfg.Settings == nil { | ||
| cfg.Settings = &userconfig.Settings{} | ||
| } | ||
| cfg.Settings.FollowupBehavior = mode | ||
| if err := cfg.Save(); err != nil { | ||
| slog.Warn("Failed to persist follow-up behavior to userconfig", "error", err) | ||
| } | ||
| }() | ||
|
|
||
| var description string | ||
| switch mode { | ||
| case userconfig.FollowupBehaviorSteer: | ||
| description = "messages sent while working will be injected mid-turn" | ||
| case userconfig.FollowupBehaviorFollowUp: | ||
| description = "messages sent while working will queue as their own turn" | ||
| } | ||
| return m, notification.SuccessCmd(fmt.Sprintf("Follow-up behavior set to %q · %s", mode, description)) | ||
| } | ||
|
|
||
| // --- Dialogs --- | ||
|
|
||
| func (m *appModel) handleShowCostDialog() (tea.Model, tea.Cmd) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HIGH: followUpQueue not drained at
RunStreamstart — orphaned follow-ups leak into the next sessionRunStreamexplicitly drainssteerQueueat startup (line 80) to clear orphaned messages from a previous cancelled session, butfollowUpQueuereceives no equivalent drain. If the user cancels a session while follow-up messages are pending (infollowupmode), those messages remain infollowUpQueue. The nextRunStreamcall — for a completely different conversation — will dequeue and process them at the end of the first agent turn (loop.go:454:r.followUpQueue.Dequeue(ctx)), silently injecting stale messages into the new session as if the user sent them there. This works in tandem with Finding 2 below.Fix: Add
r.followUpQueue.Drain(context.Background())immediately after the steer drain on line 80.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch — fixed in 4f823e4.
RunStreamnow drains both queues at entry, so a follow-up enqueued in a previously cancelled run can't leak into the next session.