返回 DeepSeek-Reasonix
session_navigation_runtime.go
根目录 / desktop / session_navigation_runtime.go
1 package main
2
3 import (
4 "slices"
5 "strings"
6
7 "reasonix/internal/control"
8 )
9
10 func (t *WorkspaceTab) sessionRuntimeLookupKeys() []string {
11 if t == nil {
12 return nil
13 }
14 seen := map[string]struct{}{}
15 var keys []string
16 add := func(raw string) {
17 key := sessionRuntimeKey(raw)
18 if key == "" {
19 return
20 }
21 if _, ok := seen[key]; ok {
22 return
23 }
24 seen[key] = struct{}{}
25 keys = append(keys, key)
26 }
27 add(t.currentSessionIdentity())
28 add(t.currentSessionPath())
29 if id := strings.TrimSpace(t.SessionID); id != "" {
30 add(sessionRoute(id))
31 // A lease protects a resource, not an arbitrary future session. Only
32 // retain the import alias while this exact imported identity is bound.
33 if _, runtime, exclusive := exclusiveSessionBinding(t.Ctrl); exclusive && runtime != nil && runtime.Ref().SessionID == id {
34 if source := runtime.Session().Manifest().Source; source != nil {
35 add(source.Path)
36 }
37 }
38 }
39 return keys
40 }
41
42 func sessionRuntimeKeysOverlap(tab *WorkspaceTab, identity string) bool {
43 want := sessionRuntimeKey(identity)
44 if want == "" || tab == nil {
45 return false
46 }
47 return slices.Contains(tab.sessionRuntimeLookupKeys(), want)
48 }
49
50 func (a *App) liveRuntimeTabMatchingLocked(exclude *WorkspaceTab, identity string) *WorkspaceTab {
51 if a == nil {
52 return nil
53 }
54 want := sessionRuntimeKey(identity)
55 match := func(tab *WorkspaceTab) bool {
56 if tab == nil || tab == exclude || tab.Ctrl == nil {
57 return false
58 }
59 if want == "" {
60 return false
61 }
62 if id, canonical := parseSessionRoute(identity); canonical {
63 if lifecycle, ok := tab.Ctrl.(control.IdentityLifecycle); ok && lifecycle.UsesExclusiveSession() {
64 ref, bound := lifecycle.SessionRef()
65 return bound && ref.SessionID == id
66 }
67 }
68 return slices.Contains(tab.sessionRuntimeLookupKeys(), want)
69 }
70 if want != "" {
71 if rt := a.runtimeBySessionKey[want]; rt != nil && rt.Owner != exclude && a.runtimeOwnerLiveLocked(rt) && rt.Phase == sessionRuntimeReady && match(rt.Owner) {
72 return rt.Owner
73 }
74 if tab := a.detachedSessions[want]; match(tab) {
75 return tab
76 }
77 }
78 for _, tab := range a.runtimeTabsLocked() {
79 if match(tab) {
80 return tab
81 }
82 }
83 return nil
84 }
85
86 func (a *App) liveRuntimeTabMatchingTopicLocked(exclude *WorkspaceTab, scope, workspaceRoot, topicID string) *WorkspaceTab {
87 topicID = strings.TrimSpace(topicID)
88 if a == nil || topicID == "" {
89 return nil
90 }
91 var found *WorkspaceTab
92 for _, tab := range a.runtimeTabsLocked() {
93 if tab == nil || tab == exclude || tab.Ctrl == nil || !tabMatchesTopicTarget(tab, scope, workspaceRoot, topicID) {
94 continue
95 }
96 if found != nil {
97 return nil
98 }
99 found = tab
100 }
101 return found
102 }
103
104 func (a *App) registerDetachedRuntimeLocked(tab *WorkspaceTab) {
105 if tab == nil {
106 return
107 }
108 a.ensureDetachedSessionsLocked()
109 for _, key := range tab.sessionRuntimeLookupKeys() {
110 a.detachedSessions[key] = tab
111 a.aliasSessionRuntimeKeyLocked(tab, key)
112 }
113 }
114
115 func (a *App) unregisterDetachedRuntimeLocked(tab *WorkspaceTab) {
116 if a.detachedSessions == nil || tab == nil {
117 return
118 }
119 for key, candidate := range a.detachedSessions {
120 if candidate == tab {
121 delete(a.detachedSessions, key)
122 }
123 }
124 }
125
126 func (a *App) aliasSessionRuntimeKeyLocked(tab *WorkspaceTab, key string) {
127 if tab == nil || key == "" {
128 return
129 }
130 if existing := a.runtimeBySessionKey[key]; existing != nil && existing.Owner != tab && a.runtimeOwnerLiveLocked(existing) {
131 return
132 }
133 rt := a.runtimeForTabLocked(tab)
134 if rt == nil {
135 a.newSessionRuntimeLocked(tab, key)
136 return
137 }
138 a.runtimeBySessionKey[key] = rt
139 }
140
141 func runtimeAttachIdentity(source *WorkspaceTab, fallback string) string {
142 if source != nil {
143 if identity := source.currentSessionIdentity(); identity != "" {
144 return identity
145 }
146 }
147 return fallback
148 }
149
150 // App.mu, runtimeRebuildMu and turnStartMu guard this handoff. A running source
151 // keeps its runtime, sink and writer; only the target reservation is replaced.
152 func (a *App) commitCanonicalRuntimeTransitionLocked(tab *WorkspaceTab, transition sessionRuntimePathTransition, preserve bool) bool {
153 if !preserve {
154 return a.commitSessionRuntimePathLocked(transition)
155 }
156 if !a.sessionRuntimePathTransitionValidLocked(transition) || !a.detachRuntimeForReplacementLocked(tab) {
157 return false
158 }
159 if a.runtimeBySessionKey[transition.targetKey] == transition.runtime {
160 delete(a.runtimeBySessionKey, transition.targetKey)
161 }
162 return true
163 }
164
165 func fenceCanonicalNavigationSink(sink *tabEventSink) {
166 if sink != nil {
167 sink.setBinding("", nil)
168 sink.clearContext()
169 }
170 }
171
171 lines GO