返回 DeepSeek-Reasonix
sessionpath.go
根目录 / internal / control / sessionpath.go
1 package control
2
3 import (
4 "context"
5 "errors"
6 "log/slog"
7 "os"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/provider"
11 )
12
13 // EnsureSessionPath pins a fresh auto-save file for this controller when none is
14 // set yet and a session dir is configured — the "fresh session" branch every
15 // surface runs right after building a controller. It is a no-op once a resume or
16 // continue has already pinned a path (SessionPath() != ""), so callers can run a
17 // conditional Resume and then invoke this unconditionally. Centralises the
18 // per-surface copies of this logic (the CLI chat/serve fresh branches and the
19 // bot's former ensureControllerSessionPath).
20 func (c *Controller) EnsureSessionPath() {
21 if _, ok := c.SessionRef(); ok {
22 return
23 }
24 if service, _, exclusive := c.v3Binding(); exclusive && service != nil {
25 if _, err := c.BindFreshSession(context.Background(), ""); err != nil {
26 c.failTurnEventLedger(err)
27 }
28 return
29 }
30 if c.SessionPath() != "" || c.SessionDir() == "" {
31 return
32 }
33 c.SetFreshSessionPath(agent.NewSessionPath(c.SessionDir(), c.Label()))
34 }
35
36 // AdoptHistory makes a freshly built controller continue an existing
37 // conversation in path: it resumes the carried messages there when there are
38 // any, otherwise just points auto-save at path. An empty path with no messages
39 // is a no-op. This is the shared kernel of the model/effort switch across the
40 // CLI, the HTTP server, and ACP — each computes path its own way
41 // (ContinueSessionPath for the CLI/serve, the pinned transcript for ACP) and
42 // hands the carried history (Controller.History()) here. Keeping the
43 // Resume/SetSessionPath choice in one place avoids the orphaned-duplicate class
44 // of bug (#2807) recurring as each surface copied it.
45 func (c *Controller) AdoptHistory(msgs []provider.Message, path string) {
46 if c.sessionEngineEnabled() {
47 if _, ok := c.SessionRef(); ok {
48 if len(msgs) > 0 {
49 if err := c.replaceSessionEventProjection(context.Background(), "explicit-history-adopt", msgs); err != nil {
50 slog.Warn("controller: record adopted v3 history", "err", err)
51 c.failTurnEventLedger(err)
52 } else {
53 c.restoreExecutorFromSessionEvents()
54 }
55 } else {
56 c.restoreExecutorFromSessionEvents()
57 }
58 return
59 }
60 if path == "" {
61 // A hot rebuild may carry an in-memory conversation that never had
62 // persistent identity. Keep it as the candidate model/UI state; the
63 // first real input will create one v3 session and seed these exact
64 // messages. This is not a committed session until that admission.
65 if len(msgs) > 0 && c.executor != nil {
66 c.executor.SetSession(agent.NewSession("").CloneWithMessages(msgs))
67 }
68 return
69 }
70 if path != "" {
71 if _, statErr := os.Stat(path); os.IsNotExist(statErr) {
72 if _, err := c.BindFreshSession(context.Background(), ""); err != nil {
73 c.failTurnEventLedger(err)
74 return
75 }
76 if len(msgs) > 0 {
77 if err := c.replaceSessionEventProjection(context.Background(), "fresh-history-adopt", msgs); err != nil {
78 c.failTurnEventLedger(err)
79 } else {
80 c.restoreExecutorFromSessionEvents()
81 }
82 }
83 return
84 }
85 if _, err := c.ContinueLegacySession(context.Background(), path, ""); err != nil {
86 slog.Warn("controller: legacy continue into v3 failed", "path", path, "err", err)
87 c.failTurnEventLedger(err)
88 }
89 }
90 return
91 }
92 if len(msgs) > 0 {
93 if path != "" {
94 if loaded, err := agent.LoadSession(path); err == nil && loaded != nil {
95 if resumed, ok := loaded.CloneWithMessagesIfCompatible(msgs); ok {
96 c.Resume(resumed, path)
97 return
98 }
99 }
100 }
101 c.Resume(agent.NewSession("").CloneWithMessages(msgs), path)
102 } else if path != "" {
103 // Even an empty transcript can carry session-scoped sidecars such as a
104 // running or blocked Goal. Resume a persisted empty session so controller
105 // rebuilds preserve that state; fall back to a plain binding for a fresh
106 // path that has not been saved yet.
107 if loaded, err := agent.LoadSession(path); err == nil && loaded != nil {
108 c.Resume(loaded, path)
109 return
110 }
111 c.SetSessionPath(path)
112 }
113 }
114
115 // AdoptRebuiltModelContext applies a model/settings rebuild to an already
116 // bound v3 session. It changes only the provider-visible context; UI history
117 // and stable message identity remain sourced from the original event stream.
118 func (c *Controller) AdoptRebuiltModelContext(msgs []provider.Message) error {
119 if !c.sessionEngineEnabled() {
120 return errors.New("model-context adoption requires an exclusive v3 session")
121 }
122 if len(msgs) == 0 {
123 if snapshot, ok := c.sessionEventSnapshot(); ok {
124 msgs = snapshot.Projection.ModelMessages
125 }
126 }
127 return c.replaceSessionModelContext(context.Background(), msgs, "agent-rebuild")
128 }
129
129 lines GO