Skip to content

Commit 9bb0ba4

Browse files
committed
fix:修复在运行中不可切换会话避免会话丢失
1 parent db79122 commit 9bb0ba4

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

internal/tui/core/app/update.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const (
4444

4545
const providerAddSelectTimeout = 10 * time.Second
4646

47+
const sessionSwitchBusyMessage = "cannot switch sessions while run or compact is active"
48+
4749
var panelOrder = []panel{panelTranscript, panelActivity, panelInput}
4850
var persistProviderUserEnvVar = config.PersistUserEnvVar
4951
var deleteProviderUserEnvVar = config.DeleteUserEnvVar
@@ -378,6 +380,12 @@ func (a App) updateInputPanel(msg tea.Msg, typed tea.KeyMsg, cmds []tea.Cmd) (te
378380
}
379381
return a, tea.Batch(cmds...)
380382
case slashCommandSession:
383+
if err := a.ensureSessionSwitchAllowed(""); err != nil {
384+
a.state.ExecutionError = err.Error()
385+
a.state.StatusText = err.Error()
386+
a.appendActivity("session", "Failed to open session picker", err.Error(), true)
387+
return a, tea.Batch(cmds...)
388+
}
381389
if err := a.refreshSessionPicker(); err != nil {
382390
a.state.ExecutionError = err.Error()
383391
a.state.StatusText = err.Error()
@@ -774,6 +782,9 @@ func (a *App) activateSelectedSession() error {
774782
if !ok {
775783
return nil
776784
}
785+
if err := a.ensureSessionSwitchAllowed(item.Summary.ID); err != nil {
786+
return err
787+
}
777788

778789
a.state.ActiveSessionID = item.Summary.ID
779790
a.state.ActiveSessionTitle = item.Summary.Title
@@ -784,6 +795,9 @@ func (a *App) activateSelectedSession() error {
784795
}
785796

786797
func (a *App) activateSessionByID(sessionID string) error {
798+
if err := a.ensureSessionSwitchAllowed(sessionID); err != nil {
799+
return err
800+
}
787801
for _, s := range a.state.Sessions {
788802
if s.ID == sessionID {
789803
a.state.ActiveSessionID = s.ID
@@ -796,6 +810,16 @@ func (a *App) activateSessionByID(sessionID string) error {
796810
return fmt.Errorf("session not found: %s", sessionID)
797811
}
798812

813+
// ensureSessionSwitchAllowed 统一阻止运行中切换到其他会话,避免 UI 脱离仍在执行的 run 上下文。
814+
func (a *App) ensureSessionSwitchAllowed(targetSessionID string) error {
815+
targetSessionID = strings.TrimSpace(targetSessionID)
816+
activeSessionID := strings.TrimSpace(a.state.ActiveSessionID)
817+
if !a.isBusy() || (targetSessionID != "" && strings.EqualFold(targetSessionID, activeSessionID)) {
818+
return nil
819+
}
820+
return fmt.Errorf(sessionSwitchBusyMessage)
821+
}
822+
799823
func (a *App) syncActiveSessionTitle() {
800824
if strings.TrimSpace(a.state.ActiveSessionID) == "" {
801825
if strings.TrimSpace(a.state.ActiveSessionTitle) == "" {
@@ -1894,6 +1918,12 @@ func (a *App) handleImmediateSlashCommand(input string) (bool, tea.Cmd) {
18941918
case slashCommandForget:
18951919
return true, a.handleForgetCommand(rest)
18961920
case slashCommandSession:
1921+
if err := a.ensureSessionSwitchAllowed(""); err != nil {
1922+
a.state.ExecutionError = err.Error()
1923+
a.state.StatusText = err.Error()
1924+
a.appendActivity("session", "Failed to open session picker", err.Error(), true)
1925+
return true, nil
1926+
}
18971927
if err := a.refreshSessionPicker(); err != nil {
18981928
a.state.ExecutionError = err.Error()
18991929
a.state.StatusText = err.Error()

internal/tui/core/app/update_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,6 +1761,35 @@ func TestUpdatePickerSessionEnterActivatesSelectedSession(t *testing.T) {
17611761
}
17621762
}
17631763

1764+
func TestUpdatePickerSessionEnterWhileBusyRejectsSwitch(t *testing.T) {
1765+
app, runtime := newTestApp(t)
1766+
now := time.Now()
1767+
runtime.listSessions = []agentsession.Summary{
1768+
{ID: "s1", Title: "One", UpdatedAt: now.Add(-time.Minute)},
1769+
{ID: "s2", Title: "Two", UpdatedAt: now},
1770+
}
1771+
if err := app.refreshSessionPicker(); err != nil {
1772+
t.Fatalf("refreshSessionPicker() error = %v", err)
1773+
}
1774+
app.state.ActiveSessionID = "s1"
1775+
app.state.ActiveSessionTitle = "One"
1776+
app.state.IsAgentRunning = true
1777+
app.openPicker(pickerSession, statusChooseSession, &app.sessionPicker, "s1")
1778+
app.sessionPicker.Select(1)
1779+
1780+
model, cmd := app.updatePicker(tea.KeyMsg{Type: tea.KeyEnter})
1781+
if cmd != nil {
1782+
t.Fatalf("expected nil cmd for rejected session switch")
1783+
}
1784+
app = model.(App)
1785+
if app.state.ActiveSessionID != "s1" {
1786+
t.Fatalf("expected active session to remain unchanged, got %q", app.state.ActiveSessionID)
1787+
}
1788+
if !strings.Contains(app.state.ExecutionError, sessionSwitchBusyMessage) {
1789+
t.Fatalf("expected busy session switch error, got %q", app.state.ExecutionError)
1790+
}
1791+
}
1792+
17641793
func TestActivateSessionByIDNotFound(t *testing.T) {
17651794
app, _ := newTestApp(t)
17661795
app.state.Sessions = []agentsession.Summary{{ID: "s1", Title: "one"}}
@@ -1786,6 +1815,25 @@ func TestHandleImmediateSlashCommandSession(t *testing.T) {
17861815
}
17871816
}
17881817

1818+
func TestHandleImmediateSlashCommandSessionWhileBusy(t *testing.T) {
1819+
app, _ := newTestApp(t)
1820+
app.state.IsAgentRunning = true
1821+
1822+
handled, cmd := app.handleImmediateSlashCommand("/session")
1823+
if !handled {
1824+
t.Fatalf("expected /session to be handled immediately")
1825+
}
1826+
if cmd != nil {
1827+
t.Fatalf("expected busy /session to avoid returning cmd")
1828+
}
1829+
if app.state.ActivePicker != pickerNone {
1830+
t.Fatalf("expected session picker to stay closed while busy")
1831+
}
1832+
if !strings.Contains(app.state.ExecutionError, sessionSwitchBusyMessage) {
1833+
t.Fatalf("expected busy session switch error, got %q", app.state.ExecutionError)
1834+
}
1835+
}
1836+
17891837
func TestRuntimeEventToolStatusHandler(t *testing.T) {
17901838
app, _ := newTestApp(t)
17911839
payload := tuiservices.RuntimeToolStatusPayload{ToolCallID: "tool-1", ToolName: "bash", Status: string(tuistate.ToolLifecyclePlanned)}

0 commit comments

Comments
 (0)