返回 DeepSeek-Reasonix
session_rebuild.go
根目录 / desktop / session_rebuild.go
1 package main
2
3 import (
4 "context"
5
6 "reasonix/internal/boot"
7 "reasonix/internal/control"
8 "reasonix/internal/session"
9 )
10
11 func desktopLegacyImportOptions(workspaceRoot string) session.CreateOptions {
12 cwd := canonicalRuntimeRoot(workspaceRoot)
13 if cwd == "" {
14 cwd = globalWorkspaceRoot()
15 }
16 return session.CreateOptions{
17 CWD: cwd, Origin: session.SessionOriginLegacyImport,
18 }
19 }
20
21 // sessionBinding is intentionally smaller than the public desktop control
22 // surface. It lets rebuild code preserve the exact host-owned Runtime without
23 // making legacy test controllers implement the final identity API.
24 type persistedSessionBinding interface {
25 SessionBinding() (*session.Service, *session.Runtime, bool)
26 }
27
28 func exclusiveSessionBinding(ctrl control.SessionAPI) (*session.Service, *session.Runtime, bool) {
29 bound, ok := ctrl.(persistedSessionBinding)
30 if !ok || bound == nil {
31 return nil, nil, false
32 }
33 service, runtime, exclusive := bound.SessionBinding()
34 return service, runtime, exclusive && service != nil && runtime != nil
35 }
36
37 // buildDesktopControllerReplacement uses boot.Rebuild for an already-bound
38 // final-format session. That path keeps the SessionRuntime and its writer while
39 // replacing only the Agent/configuration graph. Legacy controllers retain the
40 // old build-and-resume path until they cross the explicit migration boundary.
41 func buildDesktopControllerReplacement(ctx context.Context, old control.SessionAPI, opts boot.Options) (control.SessionAPI, bool, error) {
42 concrete, ok := old.(*control.Controller)
43 if !ok || concrete == nil {
44 ctrl, err := boot.Build(ctx, opts)
45 return ctrl, false, err
46 }
47 if opts.SessionService == nil {
48 ctrl, err := boot.Build(ctx, opts)
49 return ctrl, false, err
50 }
51 if opts.SessionTemp == nil {
52 opts.SessionTemp = concrete.SessionTemp()
53 }
54 if _, _, bound := exclusiveSessionBinding(old); !bound {
55 opts.SessionCreateOptions = desktopLegacyImportOptions(opts.WorkspaceRoot)
56 }
57 result, err := boot.Rebuild(ctx, concrete, opts)
58 if err != nil {
59 return nil, true, err
60 }
61 return result.Controller, true, nil
62 }
63
64 // retireReplacedController must not close a SessionRuntime still used by its
65 // replacement. ReleaseResources tears down only the retired Agent generation.
66 func retireReplacedController(old, replacement control.SessionAPI) {
67 if old == nil || old == replacement {
68 return
69 }
70 _, oldRuntime, oldExclusive := exclusiveSessionBinding(old)
71 _, newRuntime, newExclusive := exclusiveSessionBinding(replacement)
72 if oldExclusive && newExclusive && oldRuntime == newRuntime {
73 if concrete, ok := replacement.(*control.Controller); ok {
74 concrete.ActivateGoalDriverAfterRebuild()
75 }
76 if concrete, ok := old.(*control.Controller); ok {
77 concrete.ReleaseResources()
78 return
79 }
80 }
81 old.Close()
82 }
83
84 // activateReplacementController is called only inside the host's final
85 // compare-and-publish critical section. It transfers an exclusive Session
86 // Runtime without letting construction-time candidates steal Stop or commit
87 // authority from the controller still visible in the tab.
88 func activateReplacementController(old, replacement control.SessionAPI) error {
89 return control.ActivateSessionAPIReplacement(old, replacement)
90 }
91
92 func discardReplacementController(candidate, current control.SessionAPI) {
93 if candidate == nil || candidate == current {
94 return
95 }
96 _, candidateRuntime, candidateExclusive := exclusiveSessionBinding(candidate)
97 _, currentRuntime, currentExclusive := exclusiveSessionBinding(current)
98 if candidateExclusive && currentExclusive && candidateRuntime == currentRuntime {
99 if concrete, ok := candidate.(*control.Controller); ok {
100 concrete.ReleaseResources()
101 return
102 }
103 }
104 candidate.Close()
105 }
106
106 lines GO