返回 DeepSeek-Reasonix
session_open_surface.go
根目录 / desktop / session_open_surface.go
1 package main
2
3 import (
4 "fmt"
5 "os"
6
7 "reasonix/desktop/internal/workspacestate"
8 "reasonix/internal/control"
9 "reasonix/internal/session"
10 )
11
12 // surfaceForCanonicalSession returns the tab OpenSession installs ref into.
13 // The active tab wins. Without one (fresh start, remote-only layout, or the
14 // last visible session was archived), a visible tab that already runs ref is
15 // activated, then a dormant local tab is reused, and otherwise an empty
16 // surface is created for ref's workspace and bound by the caller. Opening a
17 // session must never depend on a replacement blank session having been
18 // created for it.
19 func (a *App) surfaceForCanonicalSession(ref session.SessionRef, workspace workspacestate.Workspace) (*WorkspaceTab, control.SessionAPI, bool, error) {
20 if tab, ctrl := a.tabAndCtrlByID(""); tab != nil {
21 return tab, ctrl, false, nil
22 }
23 a.mu.Lock()
24 if owner := a.liveRuntimeTabMatchingLocked(nil, sessionRoute(ref.SessionID)); owner != nil && a.tabs[owner.ID] == owner && !owner.removed {
25 a.activeTabID = owner.ID
26 a.saveTabsLocked()
27 ctrl := owner.Ctrl
28 a.mu.Unlock()
29 return owner, ctrl, false, nil
30 }
31 a.mu.Unlock()
32
33 if tab, ctrl := a.firstResolvableLocalTab(); tab != nil {
34 // A remote-only layout keeps one dormant local tab for local work;
35 // opening a session there beats creating a second surface.
36 a.mu.Lock()
37 if a.tabs[tab.ID] == tab && !tab.removed {
38 a.activeTabID = tab.ID
39 a.saveTabsLocked()
40 a.mu.Unlock()
41 return tab, ctrl, false, nil
42 }
43 a.mu.Unlock()
44 }
45
46 scope := canonicalWorkspaceScope(workspace)
47 workspaceRoot := ""
48 if scope == "project" {
49 workspaceRoot = normalizeProjectRoot(workspace.Root)
50 if workspaceRoot == "" {
51 return nil, nil, false, fmt.Errorf("session workspace root is unavailable")
52 }
53 }
54 actualRoot := desktopWorkspaceRoot(scope, workspaceRoot)
55 if scope != "project" {
56 if err := os.MkdirAll(actualRoot, 0o755); err != nil {
57 return nil, nil, false, fmt.Errorf("create global workspace: %w", err)
58 }
59 }
60 releaseAdmission, err := a.beginProjectRuntimeAdmission(scope, actualRoot)
61 if err != nil {
62 return nil, nil, false, err
63 }
64 defer releaseAdmission()
65 if scope == "project" {
66 saveWorkspace(workspaceRoot)
67 if err := a.registerProjectRoot(workspaceRoot); err != nil {
68 return nil, nil, false, err
69 }
70 }
71 model, toolApprovalMode := desktopNewSessionDefaults(scope, actualRoot)
72 tab := &WorkspaceTab{
73 Scope: scope,
74 WorkspaceRoot: actualRoot,
75 model: model,
76 mode: tabModeFromAxes(false, toolApprovalMode == control.ToolApprovalDangerFullAccess),
77 toolApprovalMode: toolApprovalMode,
78 disabledMCP: map[string]ServerView{},
79 }
80 a.mu.Lock()
81 if existing := a.activeTabLocked(); existing != nil {
82 // Another navigation published a surface meanwhile; install into it.
83 ctrl := existing.Ctrl
84 a.mu.Unlock()
85 return existing, ctrl, false, nil
86 }
87 tab.ID = a.newUniqueTabIDLocked()
88 tab.sink = &tabEventSink{tabID: tab.ID, app: a}
89 a.tabs[tab.ID] = tab
90 a.tabOrder = append(a.tabOrder, tab.ID)
91 a.activeTabID = tab.ID
92 a.saveTabsLocked()
93 a.mu.Unlock()
94 return tab, nil, true, nil
95 }
96
97 // discardUnboundSurface removes a surface created for OpenSession whose
98 // binding failed, so a failed open leaves no empty tab behind.
99 func (a *App) discardUnboundSurface(tab *WorkspaceTab) {
100 if tab == nil {
101 return
102 }
103 a.mu.Lock()
104 defer a.mu.Unlock()
105 if a.tabs[tab.ID] != tab || tab.Ctrl != nil || tab.SessionID != "" {
106 return
107 }
108 a.markTabRemovedLocked(tab)
109 delete(a.tabs, tab.ID)
110 a.removeTabOrderLocked(tab.ID)
111 if a.activeTabID == tab.ID {
112 a.activeTabID = ""
113 if len(a.tabOrder) > 0 {
114 a.activeTabID = a.tabOrder[0]
115 }
116 }
117 a.saveTabsLocked()
118 }
119
119 lines GO