返回 DeepSeek-Reasonix
session_clear.go
根目录 / desktop / session_clear.go
1 package main
2
3 import (
4 "fmt"
5
6 "reasonix/internal/agent"
7 "reasonix/internal/control"
8 "reasonix/internal/session"
9 )
10
11 // SessionClearResult is the post-clear session identity the frontend must apply
12 // atomically so hydrate/mode-switch cannot re-bind to the destroyed transcript.
13 type SessionClearResult struct {
14 SessionPath string `json:"sessionPath"`
15 SessionID string `json:"sessionId,omitempty"`
16 Session *session.SessionRef `json:"session,omitempty"`
17 SessionRevision int64 `json:"sessionRevision,omitempty"`
18 SessionDigest string `json:"sessionDigest,omitempty"`
19 SessionGeneration uint64 `json:"sessionGeneration"`
20 }
21
22 func initClearedPins(path string, newCtrl, oldCtrl control.SessionAPI, tab *WorkspaceTab) error {
23 if err := savePinnedContextState(path, []string{}); err != nil {
24 newCtrl.Close()
25 tab.releaseSessionLease()
26 oldCtrl.CloseAfterDestroy()
27 return fmt.Errorf("initialize empty pinned context for cleared session: %w", err)
28 }
29 return nil
30 }
31
32 func setFreshControllerPath(ctrl control.SessionAPI, path string) {
33 if fresh, ok := ctrl.(interface{ SetFreshSessionPath(string) }); ok {
34 fresh.SetFreshSessionPath(path)
35 } else {
36 ctrl.SetSessionPath(path)
37 }
38 }
39
40 // ClearSession discards the current conversation and rotates to a fresh unsaved one.
41 func (a *App) ClearSession() (SessionClearResult, error) {
42 return a.ClearSessionForTab("")
43 }
44
45 // ClearSessionForTab clears the requested tab regardless of later focus changes.
46 // On success it returns the replacement session identity (path/revision/digest
47 // and a tab-local generation) so the frontend can retire the old transcript
48 // without waiting for a later MetaForTab round trip.
49 func (a *App) ClearSessionForTab(tabID string) (SessionClearResult, error) {
50 tab, ctrl := a.tabAndCtrlByID(tabID)
51 if a.tabIsReadOnly(tab) {
52 return SessionClearResult{}, readOnlyChannelErr()
53 }
54 if ctrl == nil {
55 return SessionClearResult{}, a.workspaceNotReadyErr(tab)
56 }
57 if err := a.ensureTabControllerWorkspace(tab); err != nil {
58 return SessionClearResult{}, err
59 }
60 ctrl = a.controllerForTab(tab)
61 if ctrl == nil {
62 return SessionClearResult{}, a.workspaceNotReadyErr(tab)
63 }
64 if controllerHasActiveRuntimeWork(ctrl) {
65 return a.clearActiveSessionRuntime(tab, ctrl)
66 }
67 unlockRuntime := a.lockRuntimeMutation("clear session")
68 defer unlockRuntime()
69 tab.turnStartMu.Lock()
70 defer tab.turnStartMu.Unlock()
71 ctrl = a.controllerForTab(tab)
72 if ctrl == nil {
73 return SessionClearResult{}, a.workspaceNotReadyErr(tab)
74 }
75 if controllerHasActiveRuntimeWork(ctrl) {
76 return SessionClearResult{}, errTopicHasActiveWork
77 }
78 if err := ctrl.ClearSession(); err != nil {
79 a.syncTabSessionIdentity(tab, ctrl)
80 return SessionClearResult{}, err
81 }
82 a.syncTabSessionIdentity(tab, ctrl)
83 if path := ctrl.SessionPath(); path != "" {
84 if err := savePinnedContextState(path, []string{}); err != nil {
85 return SessionClearResult{}, fmt.Errorf("initialize empty pinned context for cleared session: %w", err)
86 }
87 }
88 tab.setPinnedFiles(nil)
89 if err := a.ensureTabSessionLeaseForRebuild(tab, ctrl.SessionPath(), ""); err != nil {
90 // Wails bridge return: a raw lease error would carry the session path
91 // and holder id across to the frontend.
92 return SessionClearResult{}, userFacingSessionLeaseError("", err)
93 }
94 tab.resetTelemetry(ctrl.SessionPath())
95 // Mirror the controller: ClearSession cleared the active goal.
96 a.clearTabGoal(tab)
97 a.persistTabSessionPath(tab, ctrl.SessionPath())
98 a.invalidatePromptHistoryCache()
99 return a.bumpAndSnapshotSessionClear(tab), nil
100 }
101
102 func (a *App) bumpAndSnapshotSessionClear(tab *WorkspaceTab) SessionClearResult {
103 if tab == nil {
104 return SessionClearResult{}
105 }
106 a.mu.Lock()
107 tab.SessionGeneration++
108 gen := tab.SessionGeneration
109 if tab.sink != nil {
110 tab.sink.setSessionGeneration(gen)
111 }
112 path := tab.currentSessionPath()
113 sessionID := tab.SessionID
114 if path == "" && tab.Ctrl != nil {
115 path = tab.Ctrl.SessionPath()
116 }
117 a.mu.Unlock()
118 var sessionRef *session.SessionRef
119 if sessionID != "" {
120 ref := session.SessionRef{HostID: localDesktopHostID, SessionID: sessionID}
121 sessionRef = &ref
122 }
123 var revision int64
124 var digest string
125 if meta, ok, err := agent.LoadBranchMeta(path); err == nil && ok {
126 revision = meta.Revision
127 digest = meta.ContentDigest
128 }
129 return SessionClearResult{
130 SessionPath: path, SessionID: sessionID, Session: sessionRef,
131 SessionRevision: revision, SessionDigest: digest, SessionGeneration: gen,
132 }
133 }
134
135 // clearTabGoal drops the tab's persisted goal copy so rebuilds and restarts
136 // cannot re-seed a goal the controller has already cleared on session rotation.
137 func (a *App) clearTabGoal(tab *WorkspaceTab) {
138 if tab == nil {
139 return
140 }
141 a.mu.Lock()
142 tab.goal = ""
143 if current := a.tabs[tab.ID]; current == tab {
144 a.saveTabsLocked()
145 }
146 a.mu.Unlock()
147 }
148
148 lines GO