返回 DeepSeek-Reasonix
draft_runtime.go
根目录 / desktop / draft_runtime.go
1 package main
2
3 import (
4 "errors"
5 "reflect"
6 "strings"
7
8 "reasonix/desktop/internal/draftstate"
9 "reasonix/internal/config"
10 "reasonix/internal/control"
11 )
12
13 type draftAdmissionProfile struct {
14 submissionID string
15 settings SessionDraftSettings
16 controller control.SessionAPI
17 }
18
19 func (a *App) validateDraftAdmission(tab *WorkspaceTab, submissionID string) error {
20 a.mu.RLock()
21 operationID, profile := tab.PendingCreateOperationID, tab.draftAdmission
22 a.mu.RUnlock()
23 if operationID == "" || !strings.HasPrefix(operationID, "draft-op-") && profile == nil {
24 return nil
25 }
26 if profile == nil || profile.submissionID != submissionID {
27 return errors.New("draft creation owns the first submission")
28 }
29 if a.controllerForTab(tab) != profile.controller {
30 return errors.New("draft execution owner changed before admission")
31 }
32 return a.verifyDraftRuntime(tab.ID, profile.settings)
33 }
34
35 // prepareDraftRuntime shares the rebuild/turn barriers with formal sessions.
36 // No metadata promises a profile until its actual execution owner has it.
37 func (a *App) prepareDraftRuntime(op draftstate.Operation, settings SessionDraftSettings) error {
38 a.runtimeRebuildMu.Lock()
39 defer a.runtimeRebuildMu.Unlock()
40 a.mu.RLock()
41 var tab *WorkspaceTab
42 for _, candidate := range a.runtimeTabsLocked() {
43 if candidate.SessionID == op.SessionID {
44 tab = candidate
45 break
46 }
47 }
48 a.mu.RUnlock()
49 if tab == nil {
50 return nil
51 }
52 tab.turnStartMu.Lock()
53 defer tab.turnStartMu.Unlock()
54 snap := a.tabRuntimeSnapshot(tab)
55 if snap.ctrl == nil {
56 a.mu.Lock()
57 a.publishDraftSettingsLocked(op, settings)
58 a.mu.Unlock()
59 return nil
60 }
61 if err := rebuildControllerActiveWorkErrorFor(snap.ctrl, "draft configuration"); err != nil {
62 return err
63 }
64 a.mu.RLock()
65 sameTools := reflect.DeepEqual(tab.disabledMCP, settings.DisabledMCP) || len(tab.disabledMCP) == 0 && len(settings.DisabledMCP) == 0
66 sameOrder := reflect.DeepEqual(tab.mcpOrder, settings.MCPOrder) || len(tab.mcpOrder) == 0 && len(settings.MCPOrder) == 0
67 a.mu.RUnlock()
68 effort := ""
69 if snap.effort != nil {
70 effort = *snap.effort
71 }
72 runtime := normalizedTabRuntime{collaborationMode: settings.CollaborationMode, toolApprovalMode: normalizeToolApprovalMode(settings.ToolApprovalMode), qualityFloor: settings.QualityFloor, tokenMode: snap.tokenMode}
73 if runtime.collaborationMode == "" && tabModeHasPlan(settings.Mode) {
74 runtime.collaborationMode = "plan"
75 }
76 // These setters cannot fail and run under the same turn admission barrier.
77 // Construction-dependent settings use a candidate rather than piecemeal RPCs.
78 if snap.model == settings.Model && effort == settings.Effort && sameTools && sameOrder {
79 release, err := a.lockDraftRuntimePublication(op.ID)
80 if err != nil {
81 return err
82 }
83 defer release()
84 configureControllerRuntime(snap.ctrl, nil, runtime)
85 a.mu.Lock()
86 defer a.mu.Unlock()
87 if tab.Ctrl != snap.ctrl || !a.ownsRuntimeTabLocked(tab) {
88 return errors.New("draft runtime changed")
89 }
90 a.publishDraftSettingsLocked(op, settings)
91 return nil
92 }
93 cfg, err := config.LoadForRootReadOnly(snap.workspaceRoot)
94 if err != nil {
95 return err
96 }
97 model, err := resolveDraftCreateModelStrict(cfg, settings.Model)
98 if err != nil {
99 return err
100 }
101 options := a.sessionOpenBootOptions(tab, snap, cfg, a.desktopSessionService(sessionDirForSnapshot(snap)), a.lookupSharedHost(snap.sharedHostKey), snap.workspaceRoot, model)
102 options.ConfigSnapshot = cfg
103 options.EffortOverride = nil
104 if settings.Effort != "" {
105 value := settings.Effort
106 options.EffortOverride = &value
107 }
108 candidate, bound, err := buildDesktopControllerReplacement(a.bootContext(), snap.ctrl, options)
109 if err != nil {
110 return err
111 }
112 committed := false
113 defer func() {
114 if !committed {
115 discardReplacementController(candidate, snap.ctrl)
116 }
117 }()
118 if !bound {
119 return errors.New("draft retry requires the original canonical session runtime")
120 }
121 configureControllerRuntime(candidate, snap.ctrl, runtime)
122 for name := range settings.DisabledMCP {
123 candidate.UnregisterMCPServerTools(name)
124 }
125 if _, err := normalizeRestoredControllerRuntime(candidate, runtime); err != nil {
126 return err
127 }
128 // Cancellation is durable and checked off App.mu, before compare-and-publish.
129 release, err := a.lockDraftRuntimePublication(op.ID)
130 if err != nil {
131 return err
132 }
133 defer release()
134 a.mu.Lock()
135 if tab.Ctrl != snap.ctrl || !a.ownsRuntimeTabLocked(tab) || tab.SessionID != op.SessionID {
136 a.mu.Unlock()
137 return errors.New("draft runtime changed while preparing configuration")
138 }
139 a.mu.Unlock()
140 if err := activateReplacementController(snap.ctrl, candidate); err != nil {
141 return err
142 }
143 a.mu.Lock()
144 tab.Ctrl = candidate
145 a.supersedeTabBuildLocked(tab)
146 a.publishDraftSettingsLocked(op, settings)
147 a.mu.Unlock()
148 committed = true
149 retireReplacedController(snap.ctrl, candidate)
150 a.notifyTabRuntimeRebuilt(tab)
151 return nil
152 }
153
154 func (a *App) lockDraftRuntimePublication(operationID string) (func(), error) {
155 if operationID == "" {
156 return func() {}, nil
157 }
158 release, err := a.draftStore().PublicationLease(a.bootContext(), operationID)
159 if err != nil {
160 return nil, err
161 }
162 op, err := a.draftStore().Operation(a.bootContext(), operationID)
163 if errors.Is(err, draftstate.ErrOperationNotFound) && !strings.HasPrefix(operationID, "draft-op-") {
164 release()
165 return func() {}, nil
166 }
167 if err != nil || op.Phase != "starting" {
168 release()
169 if err != nil {
170 return nil, err
171 }
172 return nil, errors.New("draft operation no longer owns runtime preparation")
173 }
174 return release, nil
175 }
176
177 func (a *App) verifyDraftRuntime(tabID string, settings SessionDraftSettings) error {
178 tab, ctrl := a.tabAndCtrlByID(tabID)
179 if tab == nil || ctrl == nil {
180 return errors.New("draft runtime is unavailable")
181 }
182 snap := a.tabRuntimeSnapshot(tab)
183 effort := ""
184 if snap.effort != nil {
185 effort = *snap.effort
186 }
187 a.mu.RLock()
188 toolsMatch := (reflect.DeepEqual(tab.disabledMCP, settings.DisabledMCP) || len(tab.disabledMCP) == 0 && len(settings.DisabledMCP) == 0) && (reflect.DeepEqual(tab.mcpOrder, settings.MCPOrder) || len(tab.mcpOrder) == 0 && len(settings.MCPOrder) == 0)
189 a.mu.RUnlock()
190 if effort != settings.Effort || !toolsMatch || snap.qualityFloor != settings.QualityFloor {
191 return errors.New("draft execution profile changed before admission")
192 }
193 model := strings.TrimSpace(settings.Model)
194 if strings.TrimSpace(snap.model) != model {
195 cfg, err := config.LoadForRootReadOnly(snap.workspaceRoot)
196 if err != nil {
197 return err
198 }
199 model, err = resolveDraftCreateModelStrict(cfg, model)
200 if err != nil {
201 return err
202 }
203 }
204 if strings.TrimSpace(snap.model) != model || normalizeToolApprovalMode(ctrl.ToolApprovalMode()) != normalizeToolApprovalMode(settings.ToolApprovalMode) {
205 return errors.New("draft runtime configuration changed before admission")
206 }
207 if actual, ok := ctrl.(interface{ ModelRef() string }); ok && actual.ModelRef() != model {
208 cfg, err := config.LoadForRootReadOnly(snap.workspaceRoot)
209 if err != nil {
210 return err
211 }
212 canonical, err := resolveDraftCreateModelStrict(cfg, settings.Model)
213 if err != nil || actual.ModelRef() != canonical {
214 return errors.New("draft Controller model differs from its frozen configuration")
215 }
216 }
217 if ctrl.PlanMode() != (settings.CollaborationMode == "plan" || settings.CollaborationMode == "" && tabModeHasPlan(settings.Mode)) {
218 return errors.New("draft collaboration mode changed before admission")
219 }
220 return nil
221 }
222
222 lines GO