返回 DeepSeek-Reasonix
manual_session_creation.go
根目录 / desktop / manual_session_creation.go
1 package main
2
3 import (
4 "context"
5 "crypto/sha256"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "strings"
10 "time"
11
12 "reasonix/desktop/internal/sessionui"
13 "reasonix/desktop/internal/workspacestate"
14 "reasonix/internal/config"
15 "reasonix/internal/control"
16 "reasonix/internal/session"
17 )
18
19 type ManualSessionCreationRequest struct {
20 OperationID string `json:"operationId"`
21 WorkspaceID string `json:"workspaceId"`
22 Scope string `json:"scope,omitempty"`
23 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
24 }
25
26 type ManualSessionCreationView struct {
27 OperationID string `json:"operationId"`
28 WorkspaceID string `json:"workspaceId"`
29 Scope string `json:"scope"`
30 WorkspaceRoot string `json:"workspaceRoot"`
31 Ref session.SessionRef `json:"ref"`
32 TopicID string `json:"topicId"`
33 Phase string `json:"phase"`
34 Error string `json:"error,omitempty"`
35 Settings SessionDraftSettings `json:"settings"`
36 Progress *ManualCreationProgress `json:"progress,omitempty"`
37 SurfaceReady bool `json:"surfaceReady,omitempty"`
38 }
39
40 func (a *App) sessionUIStore() *sessionui.Store {
41 a.sessionServicesMu.Lock()
42 defer a.sessionServicesMu.Unlock()
43 if a.sessionUI == nil {
44 a.sessionUI = sessionui.New(config.DesktopSessionUIStatePath())
45 }
46 return a.sessionUI
47 }
48
49 func (a *App) BeginManualSessionCreation(req ManualSessionCreationRequest) (result ManualSessionCreationView, err error) {
50 defer func() { result = a.creationView(result) }()
51 defer func() { err = sessionUIError(err, req.WorkspaceID, req.OperationID) }()
52 if a.shuttingDown.Load() {
53 return ManualSessionCreationView{}, errors.New("application is shutting down")
54 }
55 id := strings.TrimSpace(req.OperationID)
56 if len(id) < 8 || len(id) > 128 {
57 return ManualSessionCreationView{}, errors.New("invalid creation operation identity")
58 }
59 store := a.sessionUIStore()
60 existing, err := store.Get(a.bootContext(), "creation", id)
61 if err != nil {
62 return ManualSessionCreationView{}, err
63 }
64 if existing.Revision != "0" {
65 view, err := decodeManualCreation(existing)
66 if err != nil {
67 return view, err
68 }
69 return a.replayManualCreation(req, view)
70 }
71 workspaceID := req.WorkspaceID
72 if workspaceID == "" {
73 var err error
74 workspaceID, err = a.ensureDesktopWorkspace(a.bootContext(), req.Scope, req.WorkspaceRoot)
75 if err != nil {
76 return ManualSessionCreationView{}, err
77 }
78 }
79 state, err := a.workspaceRegistry().Load(a.bootContext())
80 if err != nil {
81 return ManualSessionCreationView{}, err
82 }
83 w, ok := state.Workspaces[workspaceID]
84 if !ok && req.WorkspaceRoot != "" {
85 w, err = workspacestate.ResolveCreationWorkspace(state, workspaceID, req.WorkspaceRoot)
86 if err != nil {
87 return ManualSessionCreationView{}, manualCreationTargetError(err)
88 }
89 workspaceID, ok = w.ID, true
90 }
91 if !ok {
92 return ManualSessionCreationView{}, manualCreationTargetError(workspacestate.ErrWorkspaceNotFound)
93 }
94 scope, root := canonicalWorkspaceScope(w), w.Root
95 if scope == "global" {
96 root = ""
97 }
98 sum := sha256.Sum256([]byte(id))
99 view := ManualSessionCreationView{OperationID: id, WorkspaceID: workspaceID, Scope: scope, WorkspaceRoot: root,
100 Ref: session.SessionRef{HostID: localDesktopHostID, SessionID: fmt.Sprintf("desktop-manual-%x", sum[:16])},
101 TopicID: fmt.Sprintf("manual-%x", sum[:16]), Phase: "reserved", Settings: a.defaultDraftSettings(scope, root)}
102 view.Settings.ModelSource = draftModelSourceExplicit
103 payload, err := json.Marshal(view)
104 if err != nil {
105 return view, err
106 }
107 record, err := store.Save(a.bootContext(), "creation", id, "0", payload)
108 if errors.Is(err, sessionui.ErrConflict) {
109 view, err = decodeManualCreation(record)
110 if err != nil {
111 return view, err
112 }
113 return a.replayManualCreation(req, view)
114 }
115 if err != nil {
116 return view, err
117 }
118 a.creationManager().Ensure(record.Key, "begin", "")
119 return view, nil
120 }
121
122 func (a *App) GetManualSessionCreation(operationID string) (result ManualSessionCreationView, err error) {
123 defer func() { result = a.creationView(result) }()
124 defer func() { err = sessionUIError(err, "", operationID) }()
125 r, err := a.sessionUIStore().Get(a.bootContext(), "creation", operationID)
126 var view ManualSessionCreationView
127 if err != nil {
128 return view, err
129 }
130 if r.Revision == "0" {
131 return view, errors.New("creation operation not found")
132 }
133 err = json.Unmarshal(r.Payload, &view)
134 return view, err
135 }
136
137 func (a *App) RetryManualSessionCreation(operationID string) (result ManualSessionCreationView, err error) {
138 defer func() { result = a.creationView(result) }()
139 defer func() { err = sessionUIError(err, "", operationID) }()
140 r, err := a.sessionUIStore().Get(a.bootContext(), "creation", operationID)
141 var view ManualSessionCreationView
142 if err != nil {
143 return view, err
144 }
145 if r.Revision == "0" {
146 return view, errors.New("creation operation not found")
147 }
148 if err = json.Unmarshal(r.Payload, &view); err != nil {
149 return view, err
150 }
151 if view.Phase != "failed" && view.Phase != "reserved" && view.Phase != "starting" {
152 return view, nil
153 }
154 a.creationManager().Ensure(r.Key, "retry", r.Revision)
155 return view, nil
156 }
157
158 func (a *App) createManualSessionRuntime(ctx context.Context, view ManualSessionCreationView, report func(string)) error {
159 tab, err := a.reserveManualSessionTab(ctx, view, report)
160 if err != nil {
161 return err
162 }
163 report("building_runtime")
164 err = a.startManualSessionTab(ctx, tab, report)
165 if err == nil {
166 a.mu.Lock()
167 tab.PendingCreateOperationID = ""
168 a.saveTabsLocked()
169 a.mu.Unlock()
170 }
171 return err
172 }
173
174 func (a *App) reserveManualSessionTab(ctx context.Context, view ManualSessionCreationView, report func(string)) (*WorkspaceTab, error) {
175 report("waiting_runtime")
176 defer a.lockRuntimeMutation("reserve manual session")()
177 if err := ctx.Err(); err != nil {
178 return nil, err
179 }
180 if a.shuttingDown.Load() {
181 return nil, errors.New("application is shutting down")
182 }
183 report("preparing_storage")
184 root := desktopWorkspaceRoot(view.Scope, view.WorkspaceRoot)
185 if view.Scope != "project" {
186 if _, err := ensureGlobalWorkspaceRoot(); err != nil {
187 return nil, err
188 }
189 }
190 w, err := a.workspaceRegistry().BeginCreateAtRoot(ctx, workspacestate.PendingCreate{OperationID: view.OperationID, WorkspaceID: view.WorkspaceID, SessionID: view.Ref.SessionID}, root)
191 if err != nil {
192 return nil, manualCreationTargetError(err)
193 }
194 view.WorkspaceID, view.Scope, view.WorkspaceRoot = w.ID, canonicalWorkspaceScope(w), w.Root
195 if view.Scope == "global" {
196 view.WorkspaceRoot = ""
197 }
198 service := a.desktopSessionService("")
199 if _, err := service.Query().Stat(ctx, view.Ref); errors.Is(err, session.ErrSessionNotFound) {
200 runtime, createErr := service.Create(ctx, session.CreateOptions{SessionID: view.Ref.SessionID, CWD: desktopWorkspaceRoot(view.Scope, view.WorkspaceRoot), Origin: session.SessionOriginNew})
201 if createErr != nil {
202 return nil, createErr
203 }
204 if err := service.SetModel(ctx, view.Ref, view.Settings.Model, ""); err != nil {
205 return nil, err
206 }
207 if _, err := runtime.Session().Flush(ctx); err != nil {
208 return nil, err
209 }
210 } else if err != nil {
211 return nil, err
212 }
213 info, err := service.Query().Stat(ctx, view.Ref)
214 if err != nil {
215 return nil, err
216 }
217 same, err := sameDesktopPathStrict(info.CWD, root)
218 if err != nil {
219 return nil, manualCreationTargetError(workspacestate.ErrCreationWorkspaceUnavailable)
220 }
221 if !same || info.Origin == "" {
222 return nil, manualCreationTargetError(workspacestate.ErrCreationWorkspaceChanged)
223 }
224 if err := a.workspaceRegistry().AttachCreatedSessionAtRoot(ctx, view.OperationID, view.WorkspaceID, view.Ref.SessionID, root); err != nil {
225 return nil, manualCreationTargetError(err)
226 }
227 if err := a.workspaceRegistry().EnsureSessionTopic(ctx, view.Ref.SessionID, view.TopicID, ""); err != nil {
228 return nil, err
229 }
230 a.emitProjectTreeChanged()
231 if meta := a.metaForDraftSession(view.Ref.SessionID); meta != nil {
232 tab, _ := a.tabAndCtrlByID(meta.ID)
233 if tab != nil {
234 return tab, nil
235 }
236 }
237 settings := view.Settings
238 tab := &WorkspaceTab{Scope: view.Scope, WorkspaceRoot: desktopWorkspaceRoot(view.Scope, view.WorkspaceRoot),
239 SessionID: view.Ref.SessionID, TopicID: view.TopicID, TopicTitle: defaultTopicTitle, topicTitleSource: topicTitleSourceAuto,
240 PendingCreateOperationID: view.OperationID, model: settings.Model, qualityFloor: settings.QualityFloor,
241 mode: tabModeFromAxes(tabModeHasPlan(settings.Mode), settings.ToolApprovalMode == control.ToolApprovalDangerFullAccess),
242 toolApprovalMode: settings.ToolApprovalMode, disabledMCP: cloneServerViewMap(settings.DisabledMCP), mcpOrder: append([]string(nil), settings.MCPOrder...)}
243 tab.SessionWorkspace.ID = view.WorkspaceID
244 if settings.Effort != "" {
245 effort := settings.Effort
246 tab.effort = &effort
247 }
248 if err := createTopicState(view.WorkspaceRoot, view.TopicID, defaultTopicTitle, topicTitleSourceAuto, time.Now().UnixMilli()); err != nil {
249 return nil, err
250 }
251 a.mu.Lock()
252 tab.ID = a.newUniqueTabIDLocked()
253 tab.sink = &tabEventSink{tabID: tab.ID, app: a}
254 a.tabs[tab.ID] = tab
255 a.tabOrder = append(a.tabOrder, tab.ID)
256 a.saveTabsLocked()
257 a.mu.Unlock()
258 return tab, nil
259 }
260
260 lines GO