返回 DeepSeek-Reasonix
session_new.go
根目录 / desktop / session_new.go
1 package main
2
3 import (
4 "fmt"
5
6 "reasonix/internal/control"
7 )
8
9 // NewSession snapshots the current conversation and rotates to a fresh one.
10 func (a *App) NewSession() error {
11 return a.NewSessionForTab("")
12 }
13
14 // NewSessionForTab snapshots and rotates the requested tab regardless of which
15 // tab becomes active while the Wails call is in flight.
16 func (a *App) NewSessionForTab(tabID string) error {
17 tab, ctrl := a.tabAndCtrlByID(tabID)
18 if a.tabIsReadOnly(tab) {
19 return readOnlyChannelErr()
20 }
21 if ctrl == nil {
22 return a.workspaceNotReadyErr(tab)
23 }
24 if err := a.ensureTabControllerWorkspace(tab); err != nil {
25 return err
26 }
27 ctrl = a.controllerForTab(tab)
28 if ctrl == nil {
29 return a.workspaceNotReadyErr(tab)
30 }
31 // Serialize the session rotation with rebuilds, foreground admission, and
32 // Pin/Unpin. The default-model rebuild starts only after these locks release.
33 unlockRuntime := a.lockRuntimeMutation("new session")
34 tab.turnStartMu.Lock()
35 released := false
36 releaseAdmission := func() {
37 if released {
38 return
39 }
40 released = true
41 tab.turnStartMu.Unlock()
42 unlockRuntime()
43 }
44 defer releaseAdmission()
45 ctrl = a.controllerForTab(tab)
46 if ctrl == nil {
47 return a.workspaceNotReadyErr(tab)
48 }
49 // Tab is already blank — skip rotation, but still apply the configured
50 // default. A reused empty tab otherwise keeps the previous session's
51 // provider after a default-model change (#9080).
52 if !controllerHasActiveRuntimeWork(ctrl) && !messagesHaveConversationContent(ctrl.History()) {
53 if err := clearBlankSessionPinnedContext(tab, ctrl); err != nil {
54 return err
55 }
56 a.persistTabSessionPath(tab, ctrl.SessionPath())
57 releaseAdmission()
58 return a.applyNewSessionDefaultModel(tab)
59 }
60
61 if err := ctrl.NewSession(); err != nil {
62 a.syncTabSessionIdentity(tab, ctrl)
63 return err
64 }
65 a.syncTabSessionIdentity(tab, ctrl)
66 if path := ctrl.SessionPath(); path != "" {
67 if err := savePinnedContextState(path, []string{}); err != nil {
68 return fmt.Errorf("initialize empty pinned context for new session: %w", err)
69 }
70 }
71 tab.setPinnedFiles(nil)
72 // The rotated session starts with zero spend: without this reset the tab
73 // telemetry keeps the previous session's totals and the status bar 会话费用
74 // silently turns into an all-sessions running total (#5850).
75 tab.resetTelemetry(tab.currentSessionIdentity())
76 // Mirror the controller: NewSession cleared the active goal, and the tab's
77 // persisted copy must follow — otherwise the next rebuild/restart would
78 // re-seed the old goal into the fresh session via SetGoal(tab.goal).
79 a.clearTabGoal(tab)
80 if err := a.assignFreshSessionTopic(tab); err != nil {
81 return fmt.Errorf("session created; persist topic presentation: %w", err)
82 }
83 a.persistTabSessionPath(tab, ctrl.SessionPath())
84 a.invalidatePromptHistoryCache()
85 a.emitProjectTreeChangedForSessionDirs(ctrl.SessionDir())
86 releaseAdmission()
87 return a.applyNewSessionDefaultModel(tab)
88 }
89
90 func (a *App) syncTabSessionIdentity(tab *WorkspaceTab, ctrl control.SessionAPI) {
91 if a == nil || tab == nil || ctrl == nil {
92 return
93 }
94 identity, ok := ctrl.(control.IdentityLifecycle)
95 if !ok || !identity.UsesExclusiveSession() {
96 return
97 }
98 ref, bound := identity.SessionRef()
99 if !bound {
100 return
101 }
102 a.mu.Lock()
103 if current := a.tabs[tab.ID]; current == tab {
104 if tab.SessionID != ref.SessionID {
105 tab.SessionGeneration++
106 if tab.sink != nil {
107 tab.sink.setSessionGeneration(tab.SessionGeneration)
108 }
109 }
110 tab.SessionID = ref.SessionID
111 tab.SessionPath = ""
112 a.bindSessionRuntimeKeyLocked(tab, tab.currentSessionIdentity())
113 }
114 a.mu.Unlock()
115 a.saveTabsFromRemote()
116 }
117
118 func clearBlankSessionPinnedContext(tab *WorkspaceTab, ctrl control.SessionAPI) error {
119 oldFiles := tab.GetPinnedFiles()
120 path := ctrl.SessionPath()
121 if path != "" {
122 if err := savePinnedContextState(path, []string{}); err != nil {
123 return err
124 }
125 }
126 if len(oldFiles) > 0 {
127 tab.setPinnedFiles(nil)
128 }
129 return nil
130 }
131
132 func installClearedTabRuntime(tab *WorkspaceTab, ctrl control.SessionAPI, sink *tabEventSink, path string) {
133 tab.Ctrl = ctrl
134 tab.sink = sink
135 tab.SessionPath = path
136 tab.Label = ctrl.Label()
137 tab.Ready = true
138 tab.setPinnedFiles(nil)
139 }
140
140 lines GO