返回 DeepSeek-Reasonix
turn_submission_app.go
根目录 / desktop / turn_submission_app.go
1 package main
2
3 import (
4 "errors"
5 "reasonix/internal/control"
6 "reasonix/internal/event"
7 "strings"
8 )
9
10 func (a *App) knownSubmission(tabID string, req control.SubmissionRequest) (bool, error) {
11 tab, ctrl := a.tabAndCtrlByID(tabID)
12 if a.tabIsReadOnly(tab) {
13 return false, readOnlyChannelErr()
14 }
15 if identified, ok := ctrl.(*control.Controller); ok && req.ID != "" {
16 _, found, err := identified.LookupSubmission(req)
17 return found, err
18 }
19 return false, nil
20 }
21
22 // A competing retry may have waited behind the first caller's tab admission.
23 func (a *App) submissionAdmissionError(tabID string, req control.SubmissionRequest, err error) error {
24 if errors.Is(err, control.ErrTurnRunning) {
25 if found, lookupErr := a.knownSubmission(tabID, req); found || lookupErr != nil {
26 return lookupErr
27 }
28 }
29 return errors.Join(control.ErrSubmissionNotAccepted, err)
30 }
31
32 func submitIdentified(ctrl control.SessionAPI, req control.SubmissionRequest, submit func()) error {
33 return submitIdentifiedWithSetup(ctrl, req, nil, submit)
34 }
35
36 func submitIdentifiedWithSetup(ctrl control.SessionAPI, req control.SubmissionRequest, setup func() error, submit func()) error {
37 if identified, ok := ctrl.(*control.Controller); ok && identified.ClassifySubmitRoute(req.Input) != control.SubmitManagementHandled {
38 if setup != nil {
39 _, err := identified.SubmitIdentifiedWithSetup(req, setup)
40 return inboxBridgeError(err)
41 }
42 _, err := identified.SubmitIdentified(req)
43 return inboxBridgeError(err)
44 }
45 if setup != nil {
46 if err := setup(); err != nil {
47 return err
48 }
49 }
50 submit()
51 return nil
52 }
53
54 type turnSubmissionState struct {
55 inFlight bool
56 submissionID string
57 }
58
59 func (t *WorkspaceTab) recordTurnStarted(now int64) int64 {
60 t.telemMu.Lock()
61 defer t.telemMu.Unlock()
62 if t.usageTelemetry.activeTurnStartedAt == 0 {
63 t.usageTelemetry.activeTurnStartedAt = now
64 }
65 return t.usageTelemetry.activeTurnStartedAt
66 }
67
68 func (t *WorkspaceTab) turnStartedAt() int64 {
69 if t == nil {
70 return 0
71 }
72 t.telemMu.Lock()
73 defer t.telemMu.Unlock()
74 return t.usageTelemetry.activeTurnStartedAt
75 }
76
77 // setBinding reroutes the sink while invalidating correlations that were
78 // created for a different frontend tab.
79 func (s *tabEventSink) setBinding(tabID string, app *App, generation ...uint64) {
80 s.mu.Lock()
81 if s.tabID != tabID {
82 s.turn.submissionID = ""
83 }
84 s.tabID = tabID
85 if len(generation) > 0 {
86 if s.sessionGeneration != generation[0] {
87 s.turn.submissionID = ""
88 }
89 s.sessionGeneration = generation[0]
90 }
91 if app != nil {
92 s.app = app
93 }
94 s.mu.Unlock()
95 }
96
97 func (s *tabEventSink) setSessionGeneration(generation uint64) {
98 if s == nil {
99 return
100 }
101 s.mu.Lock()
102 if s.sessionGeneration != generation {
103 s.turn.submissionID = ""
104 }
105 s.sessionGeneration = generation
106 s.mu.Unlock()
107 }
108
109 func (s *tabEventSink) sessionGenerationSnapshot() uint64 {
110 if s == nil {
111 return 0
112 }
113 s.mu.RLock()
114 defer s.mu.RUnlock()
115 return s.sessionGeneration
116 }
117
118 func (s *tabEventSink) setRuntimeEpoch(epoch string) {
119 if s == nil {
120 return
121 }
122 s.mu.Lock()
123 if s.runtimeEpoch != epoch {
124 s.turn.submissionID = ""
125 }
126 s.runtimeEpoch = epoch
127 s.mu.Unlock()
128 }
129
130 func (s *tabEventSink) clearContext() {
131 s.mu.Lock()
132 s.ctx = nil
133 s.turn.submissionID = ""
134 s.mu.Unlock()
135 s.runtimeEvents.Clear()
136 }
137
138 func firstSubmissionID(ids []string) string {
139 if len(ids) == 0 {
140 return ""
141 }
142 return ids[0]
143 }
144
145 func (s *tabEventSink) submissionIDSnapshot() string {
146 if s == nil {
147 return ""
148 }
149 s.mu.RLock()
150 defer s.mu.RUnlock()
151 return s.turn.submissionID
152 }
153
154 type correlatedWireEventTab struct {
155 wireEventTab
156 SubmissionID string `json:"submissionId,omitempty"`
157 }
158
159 func toWireTabWithSubmission(e event.Event, tabID, runtimeEpoch, submissionID string, turnStartedAt int64, sessionGeneration ...uint64) any {
160 wire := toWireTab(e, tabID, runtimeEpoch)
161 if len(sessionGeneration) > 0 {
162 wire.SessionGeneration = sessionGeneration[0]
163 }
164 if e.Kind == event.TurnStarted {
165 wire.TurnStartedAt = turnStartedAt
166 }
167 if submissionID == "" {
168 return wire
169 }
170 return correlatedWireEventTab{wireEventTab: wire, SubmissionID: submissionID}
171 }
172
173 // The WithID entry points correlate one optimistic desktop user item with the
174 // raw TurnDone produced by the turn that this call actually admits.
175 func (a *App) SubmitToTabWithID(tabID, input, submissionID string) error {
176 if err := validateTurnInput(input); err != nil {
177 return err
178 }
179 return a.submitToTab(tabID, input, false, submissionID)
180 }
181
182 func (a *App) SubmitDisplayToTabWithID(tabID, display, input, submissionID string) error {
183 return a.submitDisplayToTab(tabID, display, input, submissionID)
184 }
185
186 func (a *App) submitDisplayToTab(tabID, display, input, submissionID string) error {
187 if err := validateTurnInput(input); err != nil {
188 return err
189 }
190 req := control.SubmissionRequest{ID: submissionID, Input: input, Display: display}
191 if found, err := a.knownSubmission(tabID, req); found || err != nil {
192 return err
193 }
194 admission, ctrl, err := a.beginTabTurn(tabID, true, submissionID)
195 if err != nil {
196 return a.submissionAdmissionError(tabID, req, err)
197 }
198 defer admission.abort()
199 tab := admission.tab
200 if err := a.ensureTabTopicIndexedForUserTurn(tab); err != nil {
201 return err
202 }
203 if err := submitIdentified(ctrl, req, func() { ctrl.SubmitDisplay(display, input) }); err != nil {
204 return err
205 }
206 admission.finish(ctrl)
207 return nil
208 }
209
210 func (a *App) SubmitDeliveryRecoveryToTabWithID(tabID, display, input, submissionID string) error {
211 return a.submitDeliveryRecoveryToTab(tabID, display, input, submissionID)
212 }
213
214 func (a *App) submitDeliveryRecoveryToTab(tabID, display, input, submissionID string) error {
215 if err := validateTurnInput(input); err != nil {
216 return err
217 }
218 req := control.SubmissionRequest{ID: submissionID, Input: input, Display: display, Action: "delivery-recovery"}
219 if found, err := a.knownSubmission(tabID, req); found || err != nil {
220 return err
221 }
222 admission, ctrl, err := a.beginTabTurn(tabID, true, submissionID)
223 if err != nil {
224 return a.submissionAdmissionError(tabID, req, err)
225 }
226 defer admission.abort()
227 tab := admission.tab
228 if err := a.ensureTabTopicIndexedForUserTurn(tab); err != nil {
229 return err
230 }
231 if err := submitIdentified(ctrl, req, func() { ctrl.SubmitDeliveryRecovery(display, input) }); err != nil {
232 return err
233 }
234 admission.finish(ctrl)
235 return nil
236 }
237
238 func (a *App) SubmitInvocationsToTabWithID(tabID, display, input string, invocations []InvocationRequest, submissionID string) error {
239 return a.submitInvocationsToTab(tabID, display, input, invocations, submissionID)
240 }
241
242 func (a *App) submitInvocationsToTab(tabID, display, input string, invocations []InvocationRequest, submissionID string) error {
243 if err := validateInvocationTurnInput(input, invocations); err != nil {
244 return err
245 }
246 req := control.SubmissionRequest{ID: submissionID, Input: input, Display: display, Invocations: controlInvocationRequests(invocations)}
247 if found, err := a.knownSubmission(tabID, req); found || err != nil {
248 return err
249 }
250 admission, ctrl, err := a.beginTabTurn(tabID, true, submissionID)
251 if err != nil {
252 return a.submissionAdmissionError(tabID, req, err)
253 }
254 defer admission.abort()
255 tab := admission.tab
256 if err := a.ensureTabTopicIndexedForUserTurn(tab); err != nil {
257 return err
258 }
259 if err := submitIdentified(ctrl, req, func() { ctrl.SubmitInvocationDisplay(display, input, controlInvocationRequests(invocations)) }); err != nil {
260 return err
261 }
262 admission.finish(ctrl)
263 return nil
264 }
265
266 func (a *App) SubmitInitialGoalToTabWithID(
267 tabID, goal, display, input string,
268 invocations []InvocationRequest,
269 collaborationMode, toolApprovalMode, submissionID string,
270 ) ([]string, error) {
271 if err := validateInvocationTurnInput(input, invocations); err != nil {
272 return []string{}, err
273 }
274 return a.submitInitialGoalToLocalTab(
275 tabID, toolApprovalMode, goal, display, input, invocations, submissionID,
276 )
277 }
278
279 func (a *App) SubmitEditedDisplayToTabWithID(tabID, display, input, original, submissionID string) error {
280 return a.submitEditedDisplayToTab(tabID, display, input, original, submissionID)
281 }
282
283 func (a *App) submitEditedDisplayToTab(tabID, display, input, original, submissionID string) error {
284 if err := validateTurnInput(input); err != nil {
285 return err
286 }
287 req := control.SubmissionRequest{ID: submissionID, Input: input, Display: display, Original: original}
288 if found, err := a.knownSubmission(tabID, req); found || err != nil {
289 return err
290 }
291 admission, ctrl, err := a.beginTabTurn(tabID, true, submissionID)
292 if err != nil {
293 return a.submissionAdmissionError(tabID, req, err)
294 }
295 defer admission.abort()
296 tab := admission.tab
297 if err := a.ensureTabTopicIndexedForUserTurn(tab); err != nil {
298 return err
299 }
300 if err := submitIdentified(ctrl, req, func() { ctrl.SubmitEditedDisplay(display, input, original) }); err != nil {
301 return err
302 }
303 admission.finish(ctrl)
304 return nil
305 }
306
307 func (a *App) submitToTabResult(tabID, input string, fromBridge, classifyManagement bool, submissionID ...string) (control.SubmitResult, error) {
308 if found, err := a.knownSubmission(tabID, control.SubmissionRequest{ID: firstSubmissionID(submissionID), Input: input, Display: input}); found || err != nil {
309 return control.SubmitResult{Disposition: control.SubmitTurnStarted}, err
310 }
311 management := control.SubmitResult{Disposition: control.SubmitManagementHandled}
312 trimmed := strings.TrimSpace(input)
313 if trimmed == "/reload" {
314 return a.submitReloadCommand(tabID, fromBridge)
315 }
316 if trimmed == "/effort" || strings.HasPrefix(trimmed, "/effort ") {
317 tab, _ := a.tabAndCtrlByID(tabID)
318 if a.tabIsReadOnly(tab) {
319 return control.SubmitResult{}, readOnlyChannelErr()
320 }
321 if tab == nil {
322 return control.SubmitResult{}, a.workspaceNotReadyErr(tab)
323 }
324 if !fromBridge && a.botBridge != nil {
325 a.botBridge.reclaimFromDesktop(tab.ID)
326 }
327 a.runEffortCommandForTab(tabID, trimmed)
328 return management, nil
329 }
330 if classifyManagement {
331 tab, ctrl := a.tabAndCtrlByID(tabID)
332 if a.tabIsReadOnly(tab) {
333 return control.SubmitResult{}, readOnlyChannelErr()
334 }
335 if err := a.workspaceRuntimeAdmissionErr(tab, ctrl); err != nil {
336 return control.SubmitResult{}, err
337 }
338 if err := a.ensureTabControllerWorkspace(tab); err != nil {
339 return control.SubmitResult{}, err
340 }
341 ctrl = a.controllerForTab(tab)
342 if ctrl == nil {
343 return control.SubmitResult{}, a.workspaceNotReadyErr(tab)
344 }
345 managementRoute := false
346 if classifier, ok := ctrl.(interface {
347 ClassifySubmitRoute(input string) control.SubmitDisposition
348 }); ok {
349 managementRoute = classifier.ClassifySubmitRoute(input) == control.SubmitManagementHandled
350 }
351 if managementRoute {
352 // Management commands still take the tab admission lock so they cannot
353 // race an active turn or a controller replacement.
354 admission, admittedCtrl, err := a.beginTabTurn(tabID, !fromBridge, submissionID...)
355 if err != nil {
356 return control.SubmitResult{}, err
357 }
358 defer admission.abort()
359 tab = admission.tab
360 if err := a.ensureTabTopicIndexedForUserTurn(tab); err != nil {
361 return control.SubmitResult{}, err
362 }
363 if submitter, supported := admittedCtrl.(interface {
364 SubmitDisplayWithResult(display, input string) control.SubmitResult
365 }); supported {
366 result := submitter.SubmitDisplayWithResult(input, input)
367 admission.finish(admittedCtrl)
368 return result, nil
369 }
370 admittedCtrl.SubmitDisplay(input, input)
371 admission.finish(admittedCtrl)
372 return management, nil
373 }
374 }
375 admission, ctrl, err := a.beginTabTurn(tabID, !fromBridge, submissionID...)
376 if err != nil {
377 return control.SubmitResult{Disposition: control.SubmitTurnStarted}, a.submissionAdmissionError(tabID,
378 control.SubmissionRequest{ID: firstSubmissionID(submissionID), Input: input, Display: input}, err)
379 }
380 defer admission.abort()
381 tab := admission.tab
382 if err := a.ensureTabTopicIndexedForUserTurn(tab); err != nil {
383 return control.SubmitResult{}, err
384 }
385 result := control.SubmitResult{Disposition: control.SubmitTurnStarted}
386 if identified, ok := ctrl.(*control.Controller); ok && firstSubmissionID(submissionID) != "" && identified.ClassifySubmitRoute(input) != control.SubmitManagementHandled {
387 _, err := identified.SubmitIdentified(control.SubmissionRequest{ID: firstSubmissionID(submissionID), Input: input, Display: input})
388 if err != nil {
389 return control.SubmitResult{}, err
390 }
391 } else if submitter, ok := ctrl.(interface {
392 SubmitDisplayWithResult(display, input string) control.SubmitResult
393 }); ok {
394 result = submitter.SubmitDisplayWithResult(input, input)
395 } else {
396 ctrl.SubmitDisplay(input, input)
397 }
398 admission.finish(ctrl)
399 return result, nil
400 }
401
402 func (a *App) submitReloadCommand(tabID string, fromBridge bool) (control.SubmitResult, error) {
403 management := control.SubmitResult{Disposition: control.SubmitManagementHandled}
404 tab, _ := a.tabAndCtrlByID(tabID)
405 if a.tabIsReadOnly(tab) {
406 return control.SubmitResult{}, readOnlyChannelErr()
407 }
408 if tab == nil {
409 return control.SubmitResult{}, a.workspaceNotReadyErr(tab)
410 }
411 if !fromBridge && a.botBridge != nil {
412 a.botBridge.reclaimFromDesktop(tab.ID)
413 }
414 return management, a.ReloadRuntime(tab.ID)
415 }
416
416 lines GO