返回 DeepSeek-Reasonix
model_settings_startup.go
根目录 / desktop / model_settings_startup.go
1 package main
2
3 import (
4 "context"
5 "errors"
6 "reasonix/internal/agent"
7 "reasonix/internal/boot"
8 "reasonix/internal/control"
9 "strings"
10 )
11
12 // Runtime settings belong to the runtime owner, including a detached owner.
13 // Foreground actions still resolve their tab through tabByID/beginTabTurn.
14 func (a *App) ownsRuntimeTabLocked(tab *WorkspaceTab) bool {
15 if tab == nil {
16 return false
17 }
18 if a.tabs[tab.ID] == tab {
19 return true
20 }
21 for _, detached := range a.detachedSessions {
22 if detached == tab {
23 return true
24 }
25 }
26 return false
27 }
28
29 func setTabStartupError(tab *WorkspaceTab, err error) bool {
30 if tab == nil {
31 return false
32 }
33 tab.StartupErr = userFacingSessionLeaseError("", err).Error()
34 tab.StartupErrLeaseHeld = errors.Is(err, agent.ErrSessionLeaseHeld)
35 tab.modelApplication.startupRetry = errors.Is(err, errNoDesktopChatModel) || errors.Is(err, boot.ErrUnknownModel) || errors.Is(err, errModelSettingsSuperseded)
36 return tab.StartupErrLeaseHeld
37 }
38
39 func clearTabStartupError(tab *WorkspaceTab) {
40 if tab == nil {
41 return
42 }
43 tab.StartupErr = ""
44 tab.StartupErrLeaseHeld = false
45 tab.modelApplication.startupRetry = false
46 }
47
48 func (a *App) recordTabStartupFailure(tab *WorkspaceTab, buildGeneration uint64, wailsCtx context.Context, err error) {
49 a.mu.Lock()
50 if a.tabBuildSupersededLocked(tab, buildGeneration) {
51 a.mu.Unlock()
52 return
53 }
54 leaseHeld, save := a.markTabStartupFailureLocked(tab, err, keepStartupRestore)
55 tab.releaseSessionLease()
56 a.mu.Unlock()
57 a.writeTabsSaveRequest(save)
58 if leaseHeld {
59 a.scheduleDeferredStartupBuild(tab.ID)
60 tabID := tab.ID
61 // The deferred loop retries every 2s and re-enters this path. Only the
62 // first transition to lease_blocked needs the explicit meta push — a
63 // repeated push would re-fetch the same list and churn the frontend.
64 a.mu.RLock()
65 rt := a.runtimeForTabLocked(tab)
66 alreadyBlocked := rt != nil && rt.Phase == sessionRuntimeLeaseBlocked && rt.Issue != nil && rt.Issue.Code == "session_lease_held"
67 a.mu.RUnlock()
68 if alreadyBlocked {
69 a.emitReady(wailsCtx, tab.ID)
70 return
71 }
72 // Failed startup emits no agent events. Publish the lease-blocked meta
73 // explicitly so the frontend can offer takeover.
74 a.goSafe("tab-meta-push-lease", func() {
75 if a.tabs[tabID] == nil {
76 return
77 }
78 a.emitRuntimeEvent(tabMetaRefreshEventChannel, TabMetaRefreshEvent{TabID: tabID, Meta: a.MetaForTab(tabID)})
79 })
80 }
81 a.emitReady(wailsCtx, tab.ID)
82 }
83
84 func (a *App) rejectStaleStartupModelSettings(tab *WorkspaceTab, ctrl control.SessionAPI, generation uint64, ctx context.Context, abandon func()) bool {
85 stale, err := modelSettingsNeedApply(ctrl)
86 if err == nil && !stale {
87 return false
88 }
89 abandon()
90 if err == nil {
91 err = errModelSettingsSuperseded
92 }
93 a.recordTabStartupFailure(tab, generation, ctx, err)
94 if stale {
95 a.scheduleDeferredStartupBuild(tab.ID)
96 }
97 return true
98 }
99
100 func (a *App) finishStartupPublication(tab *WorkspaceTab, ctrl control.SessionAPI, wailsCtx context.Context) {
101 // A directly-opened session announces itself to a resident serve so the
102 // remote side can watch it read-only and reclaim it (see
103 // adoptSessionFromLocalServe). First-open path of the takeover flow.
104 if path := strings.TrimSpace(tab.currentSessionPath()); path != "" && !tab.ReadOnly {
105 a.attachTakeoverMirror(tab.ID, path)
106 go a.adoptSessionFromLocalServe(tab.ID, path)
107 }
108 recoverPendingTurnProjections(tab, ctrl)
109 a.emitReady(wailsCtx, tab.ID)
110 if inbox, ok := ctrl.(interface{ NotifyInboxRuntimeReady() }); ok {
111 go inbox.NotifyInboxRuntimeReady()
112 }
113 }
114
115 type tabStartupState struct {
116 err string
117 leaseHeld bool
118 ready bool
119 modelApplication tabModelApplicationState
120 }
121
122 func (t *WorkspaceTab) startupState() tabStartupState {
123 return tabStartupState{t.StartupErr, t.StartupErrLeaseHeld, t.Ready, t.modelApplication}
124 }
125
126 func (t *WorkspaceTab) restoreStartupState(state tabStartupState) {
127 t.StartupErr = state.err
128 t.StartupErrLeaseHeld = state.leaseHeld
129 t.Ready = state.ready
130 t.modelApplication = state.modelApplication
131 }
132
132 lines GO