| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | |
| 6 | "reasonix/internal/agent" |
| 7 | "reasonix/internal/control" |
| 8 | ) |
| 9 | |
| 10 | const sessionOperationHistoricalSourcePending = "historical_source_pending" |
| 11 | |
| 12 | // nativeHistoricalSource names the transcript a native legacy runtime continues |
| 13 | // in place. That runtime has no durable submission store, so the source must |
| 14 | // be prepared into a canonical session before it can accept an identified send. |
| 15 | func nativeHistoricalSource(ctrl control.SessionAPI, path, headID string) *SessionSourceRef { |
| 16 | native, ok := ctrl.(*control.Controller) |
| 17 | path = strings.TrimSpace(path) |
| 18 | if !ok || path == "" || !native.NativeLegacySession() { |
| 19 | return nil |
| 20 | } |
| 21 | path = agent.CanonicalSessionPath(path) |
| 22 | return &SessionSourceRef{HostID: localDesktopHostID, Path: path, HeadID: headID, SourceKey: desktopSourceKey(path, headID)} |
| 23 | } |
| 24 | |
| 25 | // historicalPreview reports a runtime showing a legacy source in place. Sends |
| 26 | // to it are refused, so it holds nothing the source lacks, and the source stays |
| 27 | // byte-for-byte what import and cleanup fingerprinted: it is never written. |
| 28 | func historicalPreview(ctrl control.SessionAPI) bool { |
| 29 | native, ok := ctrl.(*control.Controller) |
| 30 | return ok && native != nil && native.NativeLegacySession() && strings.TrimSpace(native.SessionPath()) != "" |
| 31 | } |
| 32 | |
| 33 | // tabHistoricalSourceLocked requires a.mu; it reports a source whether the tab |
| 34 | // was restored as a preparation shell or runs the source natively. |
| 35 | func tabHistoricalSourceLocked(tab *WorkspaceTab) *SessionSourceRef { |
| 36 | if tab == nil { |
| 37 | return nil |
| 38 | } |
| 39 | if tab.HistoricalSource != nil { |
| 40 | return tab.HistoricalSource |
| 41 | } |
| 42 | return nativeHistoricalSource(tab.Ctrl, tab.currentSessionPath(), tab.SessionHeadID) |
| 43 | } |
| 44 | |
| 45 | // identifiedSubmissionCheck runs under the turn admission locks, so the |
| 46 | // refusal names the same runtime the submission would have reached. |
| 47 | func identifiedSubmissionCheck(submissionID []string) func(control.SessionAPI) error { |
| 48 | if firstSubmissionID(submissionID) == "" { |
| 49 | return nil |
| 50 | } |
| 51 | return func(ctrl control.SessionAPI) error { |
| 52 | if nativeHistoricalSource(ctrl, ctrl.SessionPath(), "") == nil { |
| 53 | return nil |
| 54 | } |
| 55 | return newSessionOperationError(sessionOperationHistoricalSourcePending, |
| 56 | "This conversation is still a historical source. Prepare it as a session before sending new messages.") |
| 57 | } |
| 58 | } |
| 59 |