| 1 | package control |
| 2 | |
| 3 | import ( |
| 4 | "reasonix/internal/agent" |
| 5 | "reasonix/internal/provider" |
| 6 | ) |
| 7 | |
| 8 | // EnsureSessionPath pins a fresh auto-save file for this controller when none is |
| 9 | // set yet and a session dir is configured — the "fresh session" branch every |
| 10 | // surface runs right after building a controller. It is a no-op once a resume or |
| 11 | // continue has already pinned a path (SessionPath() != ""), so callers can run a |
| 12 | // conditional Resume and then invoke this unconditionally. Centralises the |
| 13 | // per-surface copies of this logic (the CLI chat/serve fresh branches and the |
| 14 | // bot's former ensureControllerSessionPath). |
| 15 | func (c *Controller) EnsureSessionPath() { |
| 16 | if c.SessionPath() != "" || c.SessionDir() == "" { |
| 17 | return |
| 18 | } |
| 19 | c.SetFreshSessionPath(agent.NewSessionPath(c.SessionDir(), c.Label())) |
| 20 | } |
| 21 | |
| 22 | // AdoptHistory makes a freshly built controller continue an existing |
| 23 | // conversation in path: it resumes the carried messages there when there are |
| 24 | // any, otherwise just points auto-save at path. An empty path with no messages |
| 25 | // is a no-op. This is the shared kernel of the model/effort switch across the |
| 26 | // CLI, the HTTP server, and ACP — each computes path its own way |
| 27 | // (ContinueSessionPath for the CLI/serve, the pinned transcript for ACP) and |
| 28 | // hands the carried history (Controller.History()) here. Keeping the |
| 29 | // Resume/SetSessionPath choice in one place avoids the orphaned-duplicate class |
| 30 | // of bug (#2807) recurring as each surface copied it. |
| 31 | func (c *Controller) AdoptHistory(msgs []provider.Message, path string) { |
| 32 | if len(msgs) > 0 { |
| 33 | if path != "" { |
| 34 | if loaded, err := agent.LoadSession(path); err == nil && loaded != nil { |
| 35 | if resumed, ok := loaded.CloneWithMessagesIfCompatible(msgs); ok { |
| 36 | c.Resume(resumed, path) |
| 37 | return |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | c.Resume(agent.NewSession("").CloneWithMessages(msgs), path) |
| 42 | } else if path != "" { |
| 43 | // Even an empty transcript can carry session-scoped sidecars such as a |
| 44 | // running or blocked Goal. Resume a persisted empty session so controller |
| 45 | // rebuilds preserve that state; fall back to a plain binding for a fresh |
| 46 | // path that has not been saved yet. |
| 47 | if loaded, err := agent.LoadSession(path); err == nil && loaded != nil { |
| 48 | c.Resume(loaded, path) |
| 49 | return |
| 50 | } |
| 51 | c.SetSessionPath(path) |
| 52 | } |
| 53 | } |
| 54 |