mslxdff 0.1.65 → 0.1.66

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.
Files changed (42) hide show
  1. package/bin/mslxdff.js +3 -3159
  2. package/package.json +1 -1
  3. package/src/cli/bootstrap.js +408 -0
  4. package/src/cli/commands/daemon.js +151 -0
  5. package/src/cli/commands/group.js +272 -0
  6. package/src/cli/commands/model.js +405 -0
  7. package/src/cli/commands/provider/add.js +106 -0
  8. package/src/cli/commands/provider/allowlist.js +99 -0
  9. package/src/cli/commands/provider/config.js +82 -0
  10. package/src/cli/commands/provider/index.js +193 -0
  11. package/src/cli/commands/provider/keys.js +135 -0
  12. package/src/cli/commands/provider/models.js +99 -0
  13. package/src/cli/commands/provider.js +1 -0
  14. package/src/cli/commands/sync.js +143 -0
  15. package/src/cli/commands/system.js +236 -0
  16. package/src/cli/commands/workbuddy.js +91 -0
  17. package/src/cli/format.js +119 -0
  18. package/src/cli/group-helpers.js +69 -0
  19. package/src/cli/help.js +66 -0
  20. package/src/cli/index.js +59 -0
  21. package/src/cli/interactive.js +84 -0
  22. package/src/cli/policy.js +119 -0
  23. package/src/cli/status.js +332 -0
  24. package/src/cli/util.js +24 -0
  25. package/src/providers/base.js +159 -0
  26. package/src/providers/generic.js +26 -166
  27. package/src/providers/openrouter.js +32 -162
  28. package/src/providers/workbuddy.js +7 -32
  29. package/src/routes/chat/gateway.js +301 -0
  30. package/src/routes/chat/index.js +1 -384
  31. package/src/state/facade.js +57 -0
  32. package/src/state/memory.js +180 -0
  33. package/src/state/persist.js +42 -0
  34. package/src/state/schemas/allowlist.js +87 -0
  35. package/src/state/schemas/group.js +31 -0
  36. package/src/state/schemas/model.js +53 -0
  37. package/src/state/schemas/peer.js +31 -0
  38. package/src/state/schemas/port.js +10 -0
  39. package/src/state/schemas/provider.js +254 -0
  40. package/src/state/schemas/token.js +43 -0
  41. package/src/state/store.js +202 -0
  42. package/src/state.js +1 -711
@@ -0,0 +1,202 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
3
+ import { writeFile } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import os from "node:os";
6
+ import { loadFromDisk, atomicWriteSync, getMtimeMs, ensureDir } from "./persist.js";
7
+
8
+ export const DEFAULT_PORT = 8989;
9
+
10
+ function isTestEnv() {
11
+ if (process.env.NODE_ENV === "test") return true;
12
+ if (process.env.MSLXDFF_STATE_FILE && String(process.env.MSLXDFF_STATE_FILE).includes("mslxdff-test")) return true;
13
+ if (process.argv.some((a) => String(a).includes("--test") || String(a).endsWith(".test.js"))) return true;
14
+ if (Array.isArray(process.execArgv) && process.execArgv.some((a) => String(a).includes("--test"))) return true;
15
+ if (process.env.NODE_TEST_CONTEXT) return true;
16
+ return false;
17
+ }
18
+
19
+ export function defaultStateFile() {
20
+ if (process.env.MSLXDFF_STATE_FILE) return process.env.MSLXDFF_STATE_FILE;
21
+ if (isTestEnv()) return join(os.tmpdir(), "mslxdff-test-state.json");
22
+ return join(os.homedir(), ".config", "mslxdff", "state.json");
23
+ }
24
+
25
+ export function tokenFile(file) {
26
+ if (process.env.MSLXDFF_TOKEN_FILE) return process.env.MSLXDFF_TOKEN_FILE;
27
+ const sf = file || defaultStateFile();
28
+ const realDefault = join(os.homedir(), ".config", "mslxdff", "state.json");
29
+ if (sf !== realDefault) return join(dirname(sf), "token");
30
+ if (isTestEnv()) return join(os.tmpdir(), "mslxdff-test-token");
31
+ return join(os.homedir(), ".config", "mslxdff", "token");
32
+ }
33
+
34
+ export function generateToken() {
35
+ return randomBytes(32).toString("hex");
36
+ }
37
+
38
+ // ---- 缓存层:读走内存,写分“热/冷”两档,热数据 500ms 批量刷盘 ----
39
+ const stateCache = new Map(); // file -> { data, dirty, timer, mtimeMs }
40
+ const FLUSH_MS = (() => {
41
+ const n = Number(process.env.MSLXDFF_STATE_FLUSH_MS);
42
+ return Number.isInteger(n) && n >= 0 ? n : 500;
43
+ })();
44
+
45
+ function getEntry(file) {
46
+ let e = stateCache.get(file);
47
+ if (!e) {
48
+ e = { data: null, dirty: false, timer: null, mtimeMs: 0 };
49
+ stateCache.set(file, e);
50
+ }
51
+ return e;
52
+ }
53
+
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
+ }
82
+
83
+ function readState(file) {
84
+ const e = getEntry(file);
85
+ if (e.data !== null) {
86
+ try {
87
+ const st = statSync(file);
88
+ if (st.mtimeMs === e.mtimeMs) return e.data;
89
+ if (e.dirty) {
90
+ const disk = loadFromDisk(file);
91
+ const diskObj = typeof disk === "object" && disk !== null ? disk : {};
92
+ const merged = mergeState(diskObj, e.data);
93
+ e.data = merged;
94
+ e.mtimeMs = st.mtimeMs;
95
+ return e.data;
96
+ }
97
+ } catch {
98
+ if (e.data) return e.data;
99
+ }
100
+ }
101
+ const disk = loadFromDisk(file);
102
+ const obj = typeof disk === "object" && disk !== null ? disk : {};
103
+ e.data = obj;
104
+ try {
105
+ const st = statSync(file);
106
+ e.mtimeMs = st.mtimeMs;
107
+ } catch {
108
+ e.mtimeMs = Date.now();
109
+ }
110
+ e.dirty = false;
111
+ return e.data;
112
+ }
113
+
114
+ function writeStateImmediate(file, patch) {
115
+ const e = getEntry(file);
116
+ const base = e.data !== null ? e.data : readState(file);
117
+ const merged = { ...base, ...patch };
118
+ e.data = merged;
119
+ e.dirty = false;
120
+ if (e.timer) {
121
+ clearTimeout(e.timer);
122
+ e.timer = null;
123
+ }
124
+ ensureDir(file);
125
+ const mtime = atomicWriteSync(file, merged);
126
+ e.mtimeMs = mtime;
127
+ return merged;
128
+ }
129
+
130
+ function scheduleFlush(file) {
131
+ const e = getEntry(file);
132
+ if (e.timer) return;
133
+ if (FLUSH_MS === 0) {
134
+ flushStateSync(file);
135
+ return;
136
+ }
137
+ e.timer = setTimeout(() => {
138
+ e.timer = null;
139
+ void flushState(file);
140
+ }, FLUSH_MS);
141
+ e.timer.unref?.();
142
+ }
143
+
144
+ async function flushState(file) {
145
+ const e = stateCache.get(file);
146
+ if (!e || !e.dirty || !e.data) return;
147
+ const snapshot = e.data;
148
+ try {
149
+ ensureDir(file);
150
+ const mtime = await atomicWriteAsync(file, snapshot);
151
+ e.dirty = false;
152
+ e.mtimeMs = mtime;
153
+ } catch {
154
+ // retain dirty
155
+ }
156
+ }
157
+
158
+ function writeStateDeferred(file, patch) {
159
+ const e = getEntry(file);
160
+ const base = e.data !== null ? e.data : readState(file);
161
+ const merged = { ...base, ...patch };
162
+ e.data = merged;
163
+ e.dirty = true;
164
+ scheduleFlush(file);
165
+ return merged;
166
+ }
167
+
168
+ export function flushStateSync(file = defaultStateFile()) {
169
+ const e = stateCache.get(file);
170
+ if (!e || !e.dirty || !e.data) return;
171
+ try {
172
+ ensureDir(file);
173
+ const mtime = atomicWriteSync(file, e.data);
174
+ e.dirty = false;
175
+ e.mtimeMs = mtime;
176
+ if (e.timer) {
177
+ clearTimeout(e.timer);
178
+ e.timer = null;
179
+ }
180
+ } catch {}
181
+ }
182
+
183
+ export function clearStateCache(file) {
184
+ if (file) stateCache.delete(file);
185
+ else stateCache.clear();
186
+ }
187
+
188
+ // internal helper for facade compat
189
+ function writeState(file, patch) {
190
+ return writeStateImmediate(file, patch);
191
+ }
192
+
193
+ export {
194
+ readState,
195
+ writeStateImmediate,
196
+ writeStateDeferred,
197
+ scheduleFlush,
198
+ flushState,
199
+ writeState,
200
+ getEntry,
201
+ stateCache,
202
+ };