返回 DeepSeek-Reasonix
tabs_persistence_types.go
根目录 / desktop / tabs_persistence_types.go
1 package main
2
3 import (
4 "bytes"
5 "encoding/json"
6
7 "reasonix/internal/boot"
8 "reasonix/internal/control"
9 )
10
11 type desktopTabEntry struct {
12 ID string `json:"id"`
13 Scope string `json:"scope"`
14 WorkspaceRoot string `json:"workspaceRoot"`
15 WorkspaceID string `json:"workspaceId,omitempty"`
16 TopicID string `json:"topicId"`
17 SessionPath string `json:"sessionPath,omitempty"`
18 SessionID string `json:"sessionId,omitempty"`
19 CreateOperationID string `json:"createOperationId,omitempty"`
20 ReadOnly bool `json:"readOnly,omitempty"`
21 TakeoverSpectator bool `json:"takeoverSpectator,omitempty"`
22 Model string `json:"model,omitempty"`
23 Effort *string `json:"effort,omitempty"`
24 TokenMode string `json:"tokenMode,omitempty"`
25 AgentPreset string `json:"agentPreset,omitempty"`
26 QualityFloor string `json:"qualityFloor,omitempty"`
27 Mode string `json:"mode,omitempty"`
28 Goal string `json:"goal,omitempty"`
29 ToolApprovalMode string `json:"toolApprovalMode,omitempty"`
30 // PinnedFiles is read-only upgrade input from the unmerged tab-scoped
31 // implementation. New writers persist pins beside the owning session.
32 PinnedFiles []string `json:"pinnedFiles,omitempty"`
33 extra map[string]json.RawMessage
34 restoreBlocked bool
35 restoreBlockReason string
36 historicalSource *SessionSourceRef
37 }
38
39 type desktopTabsFile struct {
40 Tabs []desktopTabEntry `json:"tabs"`
41 ActiveTab string `json:"activeTab"`
42 RemoteTabs []desktopRemoteTabEntry `json:"remoteTabs,omitempty"`
43 RemoteTabOrder []string `json:"remoteTabOrder,omitempty"`
44 TabOrder []string `json:"tabOrder,omitempty"`
45 extra map[string]json.RawMessage
46 }
47
48 type desktopTabWorkspace struct {
49 ID string
50 }
51
52 func persistedDesktopTabEntry(tab *WorkspaceTab) desktopTabEntry {
53 return desktopTabEntry{
54 ID: tab.ID,
55 Scope: tab.Scope,
56 WorkspaceRoot: tab.WorkspaceRoot,
57 WorkspaceID: tab.SessionWorkspace.ID,
58 TopicID: tab.TopicID,
59 SessionPath: tab.currentSessionPath(),
60 SessionID: tab.SessionID,
61 CreateOperationID: tab.PendingCreateOperationID,
62 ReadOnly: tab.ReadOnly,
63 TakeoverSpectator: tab.Takeover.Spectator,
64 Model: tab.model,
65 Effort: cloneStringPtr(tab.effort),
66 AgentPreset: boot.AgentPresetStandard,
67 TokenMode: boot.TokenModeFull,
68 QualityFloor: control.QualityFloorStandard,
69 Mode: persistedTabMode(currentTabMode(tab)),
70 Goal: persistedTabGoal(tab),
71 ToolApprovalMode: persistedToolApprovalMode(currentTabToolApprovalMode(tab)),
72 PinnedFiles: tab.pendingLegacyPinnedFilesForPersistence(),
73 extra: cloneDesktopJSONFields(tab.persistenceExtra),
74 }
75 }
76
77 func (entry *desktopTabEntry) UnmarshalJSON(body []byte) error {
78 type plain desktopTabEntry
79 var decoded plain
80 if err := json.Unmarshal(body, &decoded); err != nil {
81 return err
82 }
83 extra, err := desktopUnknownJSONFields(body,
84 "id", "scope", "workspaceRoot", "workspaceId", "topicId", "sessionPath", "sessionId", "createOperationId",
85 "readOnly", "takeoverSpectator", "model", "effort", "tokenMode", "agentPreset", "qualityFloor", "mode",
86 "goal", "toolApprovalMode", "pinnedFiles",
87 )
88 if err != nil {
89 return err
90 }
91 *entry = desktopTabEntry(decoded)
92 entry.extra = extra
93 return nil
94 }
95
96 func (entry desktopTabEntry) MarshalJSON() ([]byte, error) {
97 type plain desktopTabEntry
98 body, err := json.Marshal(plain(entry))
99 if err != nil {
100 return nil, err
101 }
102 return mergeDesktopUnknownJSONFields(body, entry.extra)
103 }
104
105 func (entry *desktopRemoteTabEntry) UnmarshalJSON(body []byte) error {
106 type plain desktopRemoteTabEntry
107 var decoded plain
108 if err := json.Unmarshal(body, &decoded); err != nil {
109 return err
110 }
111 extra, err := desktopUnknownJSONFields(body,
112 "id", "hostId", "workspace", "topicTitle", "model", "sessionName", "sessionPath", "sessionId", "sessionReset",
113 )
114 if err != nil {
115 return err
116 }
117 *entry = desktopRemoteTabEntry(decoded)
118 entry.extra = extra
119 return nil
120 }
121
122 func (entry desktopRemoteTabEntry) MarshalJSON() ([]byte, error) {
123 type plain desktopRemoteTabEntry
124 body, err := json.Marshal(plain(entry))
125 if err != nil {
126 return nil, err
127 }
128 return mergeDesktopUnknownJSONFields(body, entry.extra)
129 }
130
131 func (file *desktopTabsFile) UnmarshalJSON(body []byte) error {
132 type plain desktopTabsFile
133 var decoded plain
134 if err := json.Unmarshal(body, &decoded); err != nil {
135 return err
136 }
137 extra, err := desktopUnknownJSONFields(body, "tabs", "activeTab", "remoteTabs", "remoteTabOrder", "tabOrder")
138 if err != nil {
139 return err
140 }
141 *file = desktopTabsFile(decoded)
142 file.extra = extra
143 return nil
144 }
145
146 func (file desktopTabsFile) MarshalJSON() ([]byte, error) {
147 type plain desktopTabsFile
148 body, err := json.Marshal(plain(file))
149 if err != nil {
150 return nil, err
151 }
152 return mergeDesktopUnknownJSONFields(body, file.extra)
153 }
154
155 func desktopUnknownJSONFields(body []byte, known ...string) (map[string]json.RawMessage, error) {
156 var fields map[string]json.RawMessage
157 if err := json.Unmarshal(body, &fields); err != nil {
158 return nil, err
159 }
160 for _, key := range known {
161 delete(fields, key)
162 }
163 return fields, nil
164 }
165
166 func mergeDesktopUnknownJSONFields(known []byte, extra map[string]json.RawMessage) ([]byte, error) {
167 if len(extra) == 0 {
168 return known, nil
169 }
170 var fields map[string]json.RawMessage
171 if err := json.Unmarshal(known, &fields); err != nil {
172 return nil, err
173 }
174 for key, value := range extra {
175 if _, exists := fields[key]; !exists {
176 fields[key] = bytes.Clone(value)
177 }
178 }
179 return json.Marshal(fields)
180 }
181
182 func cloneDesktopJSONFields(fields map[string]json.RawMessage) map[string]json.RawMessage {
183 if len(fields) == 0 {
184 return nil
185 }
186 clone := make(map[string]json.RawMessage, len(fields))
187 for key, value := range fields {
188 clone[key] = bytes.Clone(value)
189 }
190 return clone
191 }
192
192 lines GO