mslxdff 0.1.66 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,68 +1,43 @@
1
1
  import { defaultStateFile, readState, writeStateImmediate } from "../store.js";
2
-
3
- export const WORKBUDDY_DEFAULT_BASE_URL = "https://copilot.tencent.com";
4
-
5
- export function normalizeBaseUrl(url) {
6
- const s = String(url || "").trim();
7
- if (!s) return "";
8
- return s.replace(/\/+$/, "");
9
- }
10
- export function normalizeAuths(list) {
11
- if (!Array.isArray(list)) return [];
12
- const out = [];
13
- for (const a of list) {
14
- if (!a || typeof a !== "object") continue;
15
- const uid = String(a.uid || a.userId || "").trim();
16
- if (!uid) continue;
17
- out.push({
18
- uid,
19
- domain: String(a.domain || "www.codebuddy.cn").trim() || "www.codebuddy.cn",
20
- enterpriseId: String(a.enterpriseId || a.enterprise_id || "").trim(),
21
- refreshToken: String(a.refreshToken || a.refresh_token || "").trim(),
22
- });
23
- }
24
- return out;
25
- }
26
- export function normalizeAllowedModel(model, providerId) {
27
- let s = String(model || "").trim();
28
- if (!s) return "";
29
- const idx = s.indexOf("/");
30
- if (idx > 0) {
31
- const head = s.slice(0, idx).toLowerCase();
32
- if (head === String(providerId || "").toLowerCase()) s = s.slice(idx + 1);
33
- }
34
- return s;
35
- }
36
- export function normalizeEndpointPath(v) {
37
- const s = String(v || "").trim();
38
- if (!s) return "";
39
- return s.startsWith("/") ? s : `/${s}`;
40
- }
41
- export function defaultModelsPath(id) {
42
- if (String(id).toLowerCase() === "workbuddy") return "/console/enterprises/personal/models";
43
- return "/models";
44
- }
45
- export function defaultChatPath(id) {
46
- if (String(id).toLowerCase() === "workbuddy") return "/v2/chat/completions";
47
- return "/chat/completions";
48
- }
2
+ export {
3
+ WORKBUDDY_DEFAULT_BASE_URL,
4
+ normalizeBaseUrl,
5
+ normalizeAuths,
6
+ normalizeAllowedModel,
7
+ normalizeEndpointPath,
8
+ defaultModelsPath,
9
+ defaultChatPath,
10
+ providerKeyEnv,
11
+ providerBaseUrlEnv,
12
+ providerShareEnv,
13
+ } from "../provider-config.js";
14
+ import {
15
+ normalizeBaseUrl as _normalizeBaseUrl,
16
+ normalizeAuths as _normalizeAuths,
17
+ normalizeAllowedModel as _normalizeAllowedModel,
18
+ normalizeEndpointPath as _normalizeEndpointPath,
19
+ defaultModelsPath as _defaultModelsPath,
20
+ defaultChatPath as _defaultChatPath,
21
+ providerKeyEnv as _providerKeyEnv,
22
+ providerBaseUrlEnv as _providerBaseUrlEnv,
23
+ providerShareEnv as _providerShareEnv,
24
+ WORKBUDDY_DEFAULT_BASE_URL as _WORKBUDDY,
25
+ } from "../provider-config.js";
26
+
27
+ // 薄门面:所有 load/save 仍在此,但归一化逻辑已收至 provider-config 单点
49
28
  export function loadProviderModelsPath(id, { file = defaultStateFile() } = {}) {
50
- const cfg = loadProviderConfigs({ file })[id];
51
- if (cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim()) return normalizeEndpointPath(cfg.modelsPath);
52
- return defaultModelsPath(id);
29
+ const cfg = readState(file).providerConfigs?.[id];
30
+ if (cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim()) return _normalizeEndpointPath(cfg.modelsPath);
31
+ return _defaultModelsPath(id);
53
32
  }
54
33
  export function loadProviderChatPath(id, { file = defaultStateFile() } = {}) {
55
- const cfg = loadProviderConfigs({ file })[id];
56
- if (cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim()) return normalizeEndpointPath(cfg.chatPath);
57
- return defaultChatPath(id);
34
+ const cfg = readState(file).providerConfigs?.[id];
35
+ if (cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim()) return _normalizeEndpointPath(cfg.chatPath);
36
+ return _defaultChatPath(id);
58
37
  }
59
38
 
60
- export const providerKeyEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_KEY`;
61
- export const providerBaseUrlEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_BASE_URL`;
62
- export const providerShareEnv = (id) => `MSLXDFF_${String(id || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_SHARE_KEYS`;
63
-
64
39
  export function loadProviderKeys(id, { file = defaultStateFile() } = {}) {
65
- const env = (process.env[providerKeyEnv(id)] || "").trim();
40
+ const env = (process.env[_providerKeyEnv(id)] || "").trim();
66
41
  if (env) return [env];
67
42
  const configs = readState(file).providerConfigs;
68
43
  const cfg = configs && typeof configs === "object" ? configs[id] : undefined;
@@ -76,118 +51,97 @@ export function loadProviderKeys(id, { file = defaultStateFile() } = {}) {
76
51
  if (Array.isArray(v)) return [...new Set(v.filter((x) => typeof x === "string" && x.trim().length))];
77
52
  return [];
78
53
  }
79
-
80
- export function loadProviderKey(id, opts = {}) {
81
- return loadProviderKeys(id, opts)[0] || "";
82
- }
83
-
54
+ export function loadProviderKey(id, opts = {}) { return loadProviderKeys(id, opts)[0] || ""; }
84
55
  export function saveProviderKeys(id, list, { file = defaultStateFile() } = {}) {
85
56
  const keys = { ...(readState(file).providerKeys || {}) };
86
57
  const clean = [...new Set((Array.isArray(list) ? list : []).map((k) => String(k || "").trim()).filter(Boolean))];
87
- if (clean.length) keys[id] = clean;
88
- else delete keys[id];
58
+ if (clean.length) keys[id] = clean; else delete keys[id];
89
59
  writeStateImmediate(file, { providerKeys: keys });
90
60
  return clean;
91
61
  }
92
-
93
62
  export function saveProviderKey(id, key, opts = {}) {
94
63
  const list = [...loadProviderKeys(id, opts)];
95
64
  const clean = String(key || "").trim();
96
65
  if (clean && !list.includes(clean)) list.push(clean);
97
66
  return saveProviderKeys(id, clean ? list : [], opts);
98
67
  }
99
-
100
- export function addProviderKey(id, key, opts = {}) {
101
- return saveProviderKey(id, key, opts);
102
- }
103
-
104
- export function removeProviderKey(id, key, opts = {}) {
105
- return removeProviderKeys(id, [key], opts);
106
- }
107
-
68
+ export function addProviderKey(id, key, opts = {}) { return saveProviderKey(id, key, opts); }
69
+ export function removeProviderKey(id, key, opts = {}) { return removeProviderKeys(id, [key], opts); }
108
70
  export function removeProviderKeys(id, targets = [], opts = {}) {
109
71
  const set = new Set((Array.isArray(targets) ? targets : [targets]).map((k) => String(k || "").trim()).filter(Boolean));
110
72
  const list = loadProviderKeys(id, opts).filter((k) => !set.has(k));
111
73
  return saveProviderKeys(id, list, opts);
112
74
  }
113
-
114
75
  export function loadProviderShareKeys(id, { file = defaultStateFile() } = {}) {
115
- const env = process.env[providerShareEnv(id)] || "";
76
+ const env = process.env[_providerShareEnv(id)] || "";
116
77
  if (env) return ["1", "true", "on", "yes"].includes(String(env).trim().toLowerCase());
117
78
  const map = readState(file).providerShareKeys;
118
79
  return !!(map && typeof map === "object" && map[id]);
119
80
  }
120
-
121
81
  export function saveProviderShareKeys(id, on, { file = defaultStateFile() } = {}) {
122
82
  const map = { ...(readState(file).providerShareKeys || {}) };
123
- if (on) map[id] = true;
124
- else delete map[id];
83
+ if (on) map[id] = true; else delete map[id];
125
84
  writeStateImmediate(file, { providerShareKeys: map });
126
85
  return !!on;
127
86
  }
128
-
129
87
  export function loadProviderConfigs({ file = defaultStateFile() } = {}) {
130
88
  const v = readState(file).providerConfigs;
131
89
  if (v && typeof v === "object" && !Array.isArray(v)) return v;
132
90
  return {};
133
91
  }
134
-
135
92
  export function loadProviderConfig(id, { file = defaultStateFile() } = {}) {
136
- const envUrl = (process.env[providerBaseUrlEnv(id)] || "").trim();
93
+ const envUrl = (process.env[_providerBaseUrlEnv(id)] || "").trim();
137
94
  const envKeys = loadProviderKeys(id, { file });
138
- if (envUrl || (process.env[providerKeyEnv(id)] || "").trim()) {
139
- const baseUrl = envUrl || loadProviderConfigs({ file })[id]?.baseUrl || (String(id).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "");
95
+ if (envUrl || (process.env[_providerKeyEnv(id)] || "").trim()) {
96
+ const baseUrl = envUrl || loadProviderConfigs({ file })[id]?.baseUrl || (String(id).toLowerCase() === "workbuddy" ? _WORKBUDDY : "");
140
97
  const cfg = loadProviderConfigs({ file })[id];
141
- const allowedModels = cfg && Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [];
142
- const auths = cfg && Array.isArray(cfg.auths) ? normalizeAuths(cfg.auths) : [];
143
- const modelsPath = cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? normalizeEndpointPath(cfg.modelsPath) : defaultModelsPath(id);
144
- const chatPath = cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? normalizeEndpointPath(cfg.chatPath) : defaultChatPath(id);
145
- if (baseUrl || envKeys.length || allowedModels.length || auths.length) return { baseUrl: normalizeBaseUrl(baseUrl), keys: envKeys, auths, allowedModels, modelsPath, chatPath };
98
+ const allowedModels = cfg && Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [];
99
+ const auths = cfg && Array.isArray(cfg.auths) ? _normalizeAuths(cfg.auths) : [];
100
+ const modelsPath = cfg && typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? _normalizeEndpointPath(cfg.modelsPath) : _defaultModelsPath(id);
101
+ const chatPath = cfg && typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? _normalizeEndpointPath(cfg.chatPath) : _defaultChatPath(id);
102
+ if (baseUrl || envKeys.length || allowedModels.length || auths.length) return { baseUrl: _normalizeBaseUrl(baseUrl), keys: envKeys, auths, allowedModels, modelsPath, chatPath };
146
103
  return null;
147
104
  }
148
105
  const configs = loadProviderConfigs({ file });
149
106
  const cfg = configs[id];
150
107
  if (cfg && typeof cfg === "object") {
151
108
  return {
152
- baseUrl: normalizeBaseUrl(cfg.baseUrl || (String(id).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "")),
109
+ baseUrl: _normalizeBaseUrl(cfg.baseUrl || (String(id).toLowerCase() === "workbuddy" ? _WORKBUDDY : "")),
153
110
  keys: Array.isArray(cfg.keys) ? [...new Set(cfg.keys.filter((x) => typeof x === "string" && x.trim().length))] : [],
154
- auths: normalizeAuths(cfg.auths),
155
- allowedModels: Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [],
156
- modelsPath: typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? normalizeEndpointPath(cfg.modelsPath) : defaultModelsPath(id),
157
- chatPath: typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? normalizeEndpointPath(cfg.chatPath) : defaultChatPath(id),
111
+ auths: _normalizeAuths(cfg.auths),
112
+ allowedModels: Array.isArray(cfg.allowedModels) ? [...new Set(cfg.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [],
113
+ modelsPath: typeof cfg.modelsPath === "string" && cfg.modelsPath.trim() ? _normalizeEndpointPath(cfg.modelsPath) : _defaultModelsPath(id),
114
+ chatPath: typeof cfg.chatPath === "string" && cfg.chatPath.trim() ? _normalizeEndpointPath(cfg.chatPath) : _defaultChatPath(id),
158
115
  };
159
116
  }
160
117
  const keys = loadProviderKeys(id, { file });
161
- if (keys.length) return { baseUrl: String(id).toLowerCase() === "workbuddy" ? WORKBUDDY_DEFAULT_BASE_URL : "", keys, auths: [], allowedModels: [], modelsPath: defaultModelsPath(id), chatPath: defaultChatPath(id) };
118
+ if (keys.length) return { baseUrl: String(id).toLowerCase() === "workbuddy" ? _WORKBUDDY : "", keys, auths: [], allowedModels: [], modelsPath: _defaultModelsPath(id), chatPath: _defaultChatPath(id) };
162
119
  return null;
163
120
  }
164
-
165
121
  export function loadProviderBaseUrl(id, opts = {}) {
166
- const env = (process.env[providerBaseUrlEnv(id)] || "").trim();
167
- if (env) return normalizeBaseUrl(env);
122
+ const env = (process.env[_providerBaseUrlEnv(id)] || "").trim();
123
+ if (env) return _normalizeBaseUrl(env);
168
124
  const cfg = loadProviderConfigs(opts)[id];
169
- if (cfg && typeof cfg.baseUrl === "string" && normalizeBaseUrl(cfg.baseUrl)) return normalizeBaseUrl(cfg.baseUrl);
170
- if (String(id || "").toLowerCase() === "workbuddy") return WORKBUDDY_DEFAULT_BASE_URL;
125
+ if (cfg && typeof cfg.baseUrl === "string" && _normalizeBaseUrl(cfg.baseUrl)) return _normalizeBaseUrl(cfg.baseUrl);
126
+ if (String(id || "").toLowerCase() === "workbuddy") return _WORKBUDDY;
171
127
  return "";
172
128
  }
173
-
174
129
  export function loadProviderAuths(id, { file = defaultStateFile() } = {}) {
175
130
  const cfg = loadProviderConfigs({ file })[id];
176
- if (cfg && Array.isArray(cfg.auths)) return normalizeAuths(cfg.auths);
131
+ if (cfg && Array.isArray(cfg.auths)) return _normalizeAuths(cfg.auths);
177
132
  return [];
178
133
  }
179
134
  export function saveProviderAuths(id, list, { file = defaultStateFile() } = {}) {
180
- const clean = normalizeAuths(list);
135
+ const clean = _normalizeAuths(list);
181
136
  const configs = { ...loadProviderConfigs({ file }) };
182
137
  const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
183
- const baseUrl = normalizeBaseUrl(cur.baseUrl || loadProviderBaseUrl(id, { file }) || "");
138
+ const baseUrl = _normalizeBaseUrl(cur.baseUrl || loadProviderBaseUrl(id, { file }) || "");
184
139
  const keys = Array.isArray(cur.keys) ? [...new Set(cur.keys.filter((x) => typeof x === "string" && x.trim().length))] : loadProviderKeys(id, { file });
185
- const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [];
186
- const modelsPath = typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "";
187
- const chatPath = typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "";
188
- if (!baseUrl && !keys.length && !clean.length && !allowedModels.length && !modelsPath && !chatPath) {
189
- delete configs[id];
190
- } else {
140
+ const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [];
141
+ const modelsPath = typeof cur.modelsPath === "string" ? _normalizeEndpointPath(cur.modelsPath) : "";
142
+ const chatPath = typeof cur.chatPath === "string" ? _normalizeEndpointPath(cur.chatPath) : "";
143
+ if (!baseUrl && !keys.length && !clean.length && !allowedModels.length && !modelsPath && !chatPath) delete configs[id];
144
+ else {
191
145
  configs[id] = { baseUrl, keys };
192
146
  if (clean.length) configs[id].auths = clean;
193
147
  if (allowedModels.length) configs[id].allowedModels = allowedModels;
@@ -197,19 +151,17 @@ export function saveProviderAuths(id, list, { file = defaultStateFile() } = {})
197
151
  writeStateImmediate(file, { providerConfigs: configs });
198
152
  return clean;
199
153
  }
200
-
201
154
  export function saveProviderBaseUrl(id, baseUrl, { file = defaultStateFile() } = {}) {
202
- const clean = normalizeBaseUrl(baseUrl);
155
+ const clean = _normalizeBaseUrl(baseUrl);
203
156
  const configs = { ...loadProviderConfigs({ file }) };
204
157
  const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
205
158
  const keys = Array.isArray(cur.keys) ? cur.keys : loadProviderKeys(id, { file });
206
- const auths = normalizeAuths(cur.auths);
207
- const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : [];
208
- const modelsPath = typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "";
209
- const chatPath = typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "";
210
- if (!clean && !keys.length && !auths.length && !allowedModels.length && !modelsPath && !chatPath) {
211
- delete configs[id];
212
- } else {
159
+ const auths = _normalizeAuths(cur.auths);
160
+ const allowedModels = Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : [];
161
+ const modelsPath = typeof cur.modelsPath === "string" ? _normalizeEndpointPath(cur.modelsPath) : "";
162
+ const chatPath = typeof cur.chatPath === "string" ? _normalizeEndpointPath(cur.chatPath) : "";
163
+ if (!clean && !keys.length && !auths.length && !allowedModels.length && !modelsPath && !chatPath) delete configs[id];
164
+ else {
213
165
  configs[id] = { baseUrl: clean, keys };
214
166
  if (auths.length) configs[id].auths = auths;
215
167
  if (allowedModels.length) configs[id].allowedModels = allowedModels;
@@ -219,23 +171,21 @@ export function saveProviderBaseUrl(id, baseUrl, { file = defaultStateFile() } =
219
171
  writeStateImmediate(file, { providerConfigs: configs });
220
172
  return clean;
221
173
  }
222
-
223
174
  export function saveProviderConfig(id, { baseUrl, keys, auths, allowedModels, modelsPath, chatPath }, { file = defaultStateFile() } = {}) {
224
- const cleanUrl = normalizeBaseUrl(baseUrl);
175
+ const cleanUrl = _normalizeBaseUrl(baseUrl);
225
176
  const cleanKeys = [...new Set((Array.isArray(keys) ? keys : []).map((k) => String(k || "").trim()).filter(Boolean))];
226
- const cleanAuths = auths === undefined ? undefined : normalizeAuths(auths);
227
- const cleanAllowed = [...new Set((Array.isArray(allowedModels) ? allowedModels : []).map((m) => normalizeAllowedModel(m, id)).filter(Boolean))];
228
- const cleanModelsPath = modelsPath === undefined ? undefined : (String(modelsPath).trim() ? normalizeEndpointPath(modelsPath) : "");
229
- const cleanChatPath = chatPath === undefined ? undefined : (String(chatPath).trim() ? normalizeEndpointPath(chatPath) : "");
177
+ const cleanAuths = auths === undefined ? undefined : _normalizeAuths(auths);
178
+ const cleanAllowed = [...new Set((Array.isArray(allowedModels) ? allowedModels : []).map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))];
179
+ const cleanModelsPath = modelsPath === undefined ? undefined : (String(modelsPath).trim() ? _normalizeEndpointPath(modelsPath) : "");
180
+ const cleanChatPath = chatPath === undefined ? undefined : (String(chatPath).trim() ? _normalizeEndpointPath(chatPath) : "");
230
181
  const configs = { ...loadProviderConfigs({ file }) };
231
182
  const cur = configs[id] && typeof configs[id] === "object" ? configs[id] : {};
232
- const finalAllowed = allowedModels === undefined ? (Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => normalizeAllowedModel(m, id)).filter(Boolean))] : []) : cleanAllowed;
233
- const finalAuths = cleanAuths === undefined ? normalizeAuths(cur.auths) : cleanAuths;
234
- const finalModelsPath = cleanModelsPath === undefined ? (typeof cur.modelsPath === "string" ? normalizeEndpointPath(cur.modelsPath) : "") : cleanModelsPath;
235
- const finalChatPath = cleanChatPath === undefined ? (typeof cur.chatPath === "string" ? normalizeEndpointPath(cur.chatPath) : "") : cleanChatPath;
236
- if (!cleanUrl && !cleanKeys.length && !finalAllowed.length && !finalAuths.length && !finalModelsPath && !finalChatPath) {
237
- delete configs[id];
238
- } else {
183
+ const finalAllowed = allowedModels === undefined ? (Array.isArray(cur.allowedModels) ? [...new Set(cur.allowedModels.map((m) => _normalizeAllowedModel(m, id)).filter(Boolean))] : []) : cleanAllowed;
184
+ const finalAuths = cleanAuths === undefined ? _normalizeAuths(cur.auths) : cleanAuths;
185
+ const finalModelsPath = cleanModelsPath === undefined ? (typeof cur.modelsPath === "string" ? _normalizeEndpointPath(cur.modelsPath) : "") : cleanModelsPath;
186
+ const finalChatPath = cleanChatPath === undefined ? (typeof cur.chatPath === "string" ? _normalizeEndpointPath(cur.chatPath) : "") : cleanChatPath;
187
+ if (!cleanUrl && !cleanKeys.length && !finalAllowed.length && !finalAuths.length && !finalModelsPath && !finalChatPath) delete configs[id];
188
+ else {
239
189
  configs[id] = { baseUrl: cleanUrl, keys: cleanKeys };
240
190
  if (finalAuths.length) configs[id].auths = finalAuths;
241
191
  if (finalAllowed.length) configs[id].allowedModels = finalAllowed;
@@ -4,6 +4,7 @@ import { writeFile } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
5
5
  import os from "node:os";
6
6
  import { loadFromDisk, atomicWriteSync, getMtimeMs, ensureDir } from "./persist.js";
7
+ import { mergeState, COLD_WINS } from "./merge.js";
7
8
 
8
9
  export const DEFAULT_PORT = 8989;
9
10
 
@@ -51,34 +52,7 @@ function getEntry(file) {
51
52
  return e;
52
53
  }
53
54
 
54
- // ---- 合并规则:coldWins 以 disk 为准,hotWins 以 mem 为准 ----
55
- const COLD_WINS = new Set([
56
- "providerConfigs",
57
- "providerKeys",
58
- "providerShareKeys",
59
- "port",
60
- "token",
61
- "preferredModel",
62
- "modelPicks",
63
- "peers",
64
- "groups",
65
- "groupsJoined",
66
- "bans",
67
- "createdAt",
68
- ]);
69
-
70
- function mergeState(disk, mem) {
71
- const merged = { ...disk, ...mem };
72
- for (const k of COLD_WINS) {
73
- if (disk[k] !== undefined) merged[k] = disk[k];
74
- else if (!(k in disk) && k in mem) {
75
- // deletion on disk: respect deletion by removing from merged if disk intentionally lacks key?
76
- // Preserve original behavior: keep mem's stale value (do not delete).
77
- // Uncomment to support true deletion: delete merged[k];
78
- }
79
- }
80
- return merged;
81
- }
55
+ // 合并规则已抽至 merge.js 单一真相
82
56
 
83
57
  function readState(file) {
84
58
  const e = getEntry(file);