返回 DeepSeek-Reasonix
remote_tab_pending_selection.go
根目录 / desktop / remote_tab_pending_selection.go
1 package main
2
3 import (
4 "strings"
5 )
6
7 // remoteTabPendingOpenSelection retains the newest session-selection intent
8 // while an existing shell is still attaching. The local identity must not
9 // advance until Serve can accept the matching /resume or /new request.
10 type remoteTabPendingOpenSelection struct {
11 name string
12 sessionID string
13 path string
14 title string
15 newSession bool
16 reuseBlank bool
17 revision uint64
18 deferred bool
19 identityCommitted bool
20 previous *remoteTabOpenSelection
21 }
22
23 func newRemoteTabPendingOpenSelection(opts RemoteTabOpenOptions) *remoteTabPendingOpenSelection {
24 return &remoteTabPendingOpenSelection{
25 name: strings.TrimSpace(opts.SessionName), sessionID: strings.TrimSpace(opts.SessionID), path: strings.TrimSpace(opts.SessionPath),
26 title: strings.TrimSpace(opts.SessionTitle), newSession: opts.NewSession,
27 }
28 }
29
30 // consumeQueuedRemoteTabOpenSelectionLocked retires the ready-tab handoff once
31 // its resume goroutine passes the revision fence. selectionMu keeps newer
32 // registrations out until this request and any rollback finish. Caller holds
33 // remoteTabMu.
34 func consumeQueuedRemoteTabOpenSelectionLocked(tab *remoteTab, revision uint64) {
35 if revision == 0 || tab.pendingSelection == nil || tab.pendingSelection.deferred || tab.pendingSelection.revision != revision {
36 return
37 }
38 tab.pendingSelection = nil
39 }
40
41 func requeueRemoteTabOpenSelectionLocked(tab *remoteTab, selection *remoteTabPendingOpenSelection) {
42 pending := tab.pendingSelection
43 if pending != nil && (pending.revision == 0 || pending.revision > selection.revision) {
44 return
45 }
46 tab.pendingSelection = selection
47 }
48
49 // applyPendingRemoteTabOpenSelection commits only the newest deferred intent
50 // once the shell reaches ready, then uses the normal transition path.
51 func (a *App) applyPendingRemoteTabOpenSelection(tabID string) {
52 a.tabSelectionMu.Lock()
53 defer a.tabSelectionMu.Unlock()
54 a.remoteTabMu.Lock()
55 tab := a.remoteTabs[tabID]
56 a.remoteTabMu.Unlock()
57 if tab == nil {
58 return
59 }
60 tab.routeEventMu.Lock()
61 a.remoteTabMu.Lock()
62 current := a.remoteTabs[tabID]
63 if current != tab || current.state != "ready" || current.pendingSelection == nil || !current.pendingSelection.deferred {
64 a.remoteTabMu.Unlock()
65 tab.routeEventMu.Unlock()
66 return
67 }
68 selection := current.pendingSelection
69 current.pendingSelection = nil
70 if selection.revision == 0 {
71 current.selectionRevision++
72 selection.revision = current.selectionRevision
73 } else if current.selectionRevision != selection.revision {
74 a.remoteTabMu.Unlock()
75 tab.routeEventMu.Unlock()
76 return
77 }
78 if selection.newSession {
79 // Recheck the authoritative state at application time. The tab may have
80 // earned content while the shell was reconnecting, so reuseBlank from
81 // registration is only a snapshot and cannot discard this newer intent.
82 reuseCurrentBlank := current.session.reset
83 a.remoteTabMu.Unlock()
84 tab.routeEventMu.Unlock()
85 if reuseCurrentBlank {
86 return
87 }
88 if err := a.resetRemoteTabSession(tabID); err != nil {
89 a.emitRemoteTabState(tabID, "ready", err.Error())
90 }
91 return
92 }
93 previous := selection.previous
94 identityChanged := !selection.identityCommitted
95 if previous == nil {
96 previous = &remoteTabOpenSelection{
97 session: current.session, topicTitle: current.topicTitle,
98 currentPath: current.routing.currentPath,
99 pending: cloneRemotePendingEvents(current.pendingEvents), runtime: current.runtime,
100 revision: selection.revision,
101 }
102 }
103 if identityChanged {
104 current.session.newSession = false
105 current.session.name = selection.name
106 current.session.sessionID = selection.sessionID
107 current.session.path = selection.path
108 if selection.title != "" {
109 current.topicTitle = selection.title
110 }
111 if route := remoteSessionIdentityRoute(selection.path, selection.sessionID); route != "" {
112 // Same provisional gate as a ready-tab registration: the route is
113 // committed before Serve confirms it through the async /resume.
114 switching := current.routing.currentPath != route
115 commitRemoteTabAttachRoute(current, route, false)
116 if switching {
117 current.routing.rehydratingPath = route
118 }
119 }
120 }
121 selection.deferred = false
122 selection.identityCommitted = true
123 selection.previous = previous
124 current.pendingSelection = selection
125 current.err = ""
126 meta := remoteTabMetaLocked(current)
127 a.remoteTabMu.Unlock()
128 tab.routeEventMu.Unlock()
129
130 if identityChanged {
131 a.emitRemoteEvent("remote-tab:updated", meta)
132 a.saveTabsFromRemote()
133 }
134 a.resumeRemoteTabOpenAsync(tabID, selection.name, selection.path, selection.title, previous)
135 }
136
137 func (a *App) resumeRemoteTabOpenAsync(tabID, name, sessionPath, sessionTitle string, selection *remoteTabOpenSelection) {
138 revision := uint64(0)
139 if selection != nil {
140 if selection.revision == 0 {
141 a.remoteTabMu.Lock()
142 if current := a.remoteTabs[tabID]; current != nil {
143 selection.revision = current.selectionRevision
144 }
145 a.remoteTabMu.Unlock()
146 }
147 revision = selection.revision
148 }
149 a.goRemoteTabSafe("remoteTabResume", func() {
150 a.remoteTabMu.Lock()
151 tab := a.remoteTabs[tabID]
152 a.remoteTabMu.Unlock()
153 if tab == nil {
154 return
155 }
156 func() {
157 tab.selectionMu.Lock()
158 defer tab.selectionMu.Unlock()
159 a.resumeRemoteTabSessionPathForOpenSelection(tabID, name, sessionPath, sessionTitle, revision, selection)
160 }()
161 a.applyPendingRemoteTabOpenSelection(tabID)
162 })
163 }
164
165 func restoreRemoteTabOpenSelectionLocked(current *remoteTab, previous *remoteTabOpenSelection) {
166 current.session = previous.session
167 current.topicTitle = previous.topicTitle
168 current.routing.currentPath = previous.currentPath
169 current.routing.pathRevision++
170 current.routing.revision++
171 current.routing.rehydratingPath = ""
172 current.routing.rehydratingFrames = nil
173 current.pendingEvents = cloneRemotePendingEvents(previous.pending)
174 restoredRuntime := previous.runtime
175 restoredRuntime.revision = max(current.runtime.revision, previous.runtime.revision) + 1
176 current.runtime = restoredRuntime
177 }
178
178 lines GO