返回 DeepSeek-Reasonix
session_source_target.go
根目录 / desktop / session_source_target.go
1 package main
2
3 import (
4 "encoding/json"
5 "errors"
6 "net/url"
7 "reasonix/internal/session"
8 "strings"
9 )
10
11 // Topic-only navigation remains a compatibility entrypoint. It may create an
12 // empty surface, but must never choose one of several durable sessions.
13 func (a *App) resolveTopicOpenPath(scope, root, topicID string) (string, error) {
14 if strings.TrimSpace(topicID) != "" {
15 target, err := a.resolveSessionTarget(SessionSelector{TopicID: topicID})
16 if err == nil && target.SessionPath != "" {
17 return target.SessionPath, nil
18 }
19 var operationError *SessionOperationError
20 if err != nil && (!errors.As(err, &operationError) || operationError.Code != sessionOperationTargetNotFound) {
21 return "", err
22 }
23 }
24 path, _ := a.findTopicSessionForTarget(scope, root, topicID)
25 return path, nil
26 }
27
28 // Head-specific writes/opening take ownership through the existing migration
29 // journal. Resolving or listing a source itself remains read-only.
30 func (a *App) resolveSessionMutationTarget(selector SessionSelector) (SessionTarget, error) {
31 target, err := a.resolveSessionTarget(selector)
32 if err != nil || target.Source == nil {
33 return target, err
34 }
35 view, err := a.PrepareSession(SessionSelector{Source: target.Source, TopicID: target.TopicID})
36 if err != nil {
37 return SessionTarget{}, err
38 }
39 c := &a.historicalImports
40 c.mu.Lock()
41 call := c.operations[view.OperationID]
42 c.mu.Unlock()
43 if call == nil {
44 return SessionTarget{}, newSessionOperationError("target_changed", "The source preparation task is unavailable.")
45 }
46 result, err := waitHistoricalImport(call)
47 if err != nil {
48 return SessionTarget{}, err
49 }
50 return a.resolveCanonicalSessionTarget(result.Session, target.TopicID)
51 }
52
53 func parseSessionSourceRoute(route string) (*SessionSourceRef, error) {
54 const prefix = "session-source:"
55 if !strings.HasPrefix(route, prefix) {
56 return nil, nil
57 }
58 text, err := url.PathUnescape(strings.TrimPrefix(route, prefix))
59 if err != nil {
60 return nil, err
61 }
62 var source SessionSourceRef
63 if err := json.Unmarshal([]byte(text), &source); err != nil {
64 return nil, err
65 }
66 return &source, nil
67 }
68
69 // Session title projection is addressed by the same durable ID as its write.
70 // A topic may gain another branch while an asynchronous title is generated.
71 func (a *App) updateCanonicalSessionTitle(ref session.SessionRef, title, source string) {
72 a.mu.Lock()
73 defer a.mu.Unlock()
74 for _, tab := range a.runtimeTabsLocked() {
75 if tab != nil && tab.SessionID == ref.SessionID {
76 tab.TopicTitle, tab.topicTitleSource = title, source
77 }
78 }
79 a.saveTabsLocked()
80 }
81
81 lines GO