mslxdff 0.1.72 → 0.1.74

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.72",
3
+ "version": "0.1.74",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,194 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const TASK_NAME = "mslxdff";
8
+ const SERVICE_NAME = "mslxdff";
9
+
10
+ function isWin() { return process.platform === "win32"; }
11
+
12
+ function nodePath() { return process.execPath; }
13
+
14
+ function scriptPath() {
15
+ // src/autostart.js -> ../bin/mslxdff.js
16
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "bin", "mslxdff.js");
17
+ }
18
+
19
+ function execAsync(file, args, opts = {}) {
20
+ return new Promise((resolve) => {
21
+ execFile(file, args, { windowsHide: true, timeout: 8000, ...opts }, (err, stdout, stderr) => {
22
+ resolve({ err, stdout: String(stdout || ""), stderr: String(stderr || ""), code: err?.code ?? 0 });
23
+ });
24
+ });
25
+ }
26
+
27
+ // ---------- Windows: schtasks ----------
28
+ async function winRegQuery() {
29
+ const r = await execAsync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", TASK_NAME]);
30
+ if (!r.err && r.stdout.includes(TASK_NAME)) return { exists: true, raw: r.stdout, method: "registry" };
31
+ return { exists: false, raw: r.stdout || r.stderr };
32
+ }
33
+
34
+ async function winQuery() {
35
+ const r = await execAsync("schtasks", ["/query", "/tn", TASK_NAME, "/fo", "LIST", "/v"]);
36
+ if (!r.err) {
37
+ const out = r.stdout || "";
38
+ if (out.includes(TASK_NAME)) return { exists: true, raw: out, method: "schtasks" };
39
+ }
40
+ // 回退查注册表
41
+ const reg = await winRegQuery();
42
+ if (reg.exists) return { exists: true, raw: reg.raw, method: "registry" };
43
+ return { exists: false, raw: r.stderr || r.stdout };
44
+ }
45
+
46
+ async function winRegEnable() {
47
+ const node = nodePath();
48
+ const script = scriptPath();
49
+ const cmd = `"${node}" "${script}"`;
50
+ const r = await execAsync("reg", ["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", TASK_NAME, "/t", "REG_SZ", "/d", cmd, "/f"]);
51
+ if (!r.err) return { ok: true, method: "registry:HKCU\\Run", stdout: r.stdout };
52
+ return { ok: false, error: (r.stderr || r.stdout || r.err?.message || "").slice(0, 800) };
53
+ }
54
+
55
+ async function winEnable() {
56
+ const node = nodePath();
57
+ const script = scriptPath();
58
+ const tr = `"${node}" "${script}"`;
59
+ // 1) schtasks ONLOGON(需任务计划权限,部分受限环境会拒绝访问)
60
+ let r = await execAsync("schtasks", ["/create", "/tn", TASK_NAME, "/tr", tr, "/sc", "onlogon", "/f"]);
61
+ if (!r.err) return { ok: true, method: "schtasks:onlogon", stdout: r.stdout };
62
+ r = await execAsync("schtasks", ["/create", "/tn", TASK_NAME, "/tr", tr, "/sc", "onlogon", "/rl", "highest", "/f"]);
63
+ if (!r.err) return { ok: true, method: "schtasks:onlogon:highest", stdout: r.stdout };
64
+ // 2) 回退注册表 HKCU Run(无需管理员,当前用户登录即起,最兼容)
65
+ const reg = await winRegEnable();
66
+ if (reg.ok) return reg;
67
+ // 3) 最后尝试 ONSTART + SYSTEM
68
+ r = await execAsync("schtasks", ["/create", "/tn", TASK_NAME, "/tr", tr, "/sc", "onstart", "/ru", "SYSTEM", "/f"]);
69
+ if (!r.err) return { ok: true, method: "schtasks:onstart:SYSTEM", stdout: r.stdout };
70
+ const msg = r.stderr || r.stdout || reg.error || "";
71
+ // 若错误含拒绝访问,明确提示注册表已成功则不报错
72
+ if (reg.ok) return reg;
73
+ return { ok: false, error: (msg || "schtasks failed").slice(0, 800), stdout: r.stdout, stderr: r.stderr };
74
+ }
75
+
76
+ async function winRegDisable() {
77
+ const r = await execAsync("reg", ["delete", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", TASK_NAME, "/f"]);
78
+ if (!r.err) return { ok: true, stdout: r.stdout };
79
+ const msg = r.stderr || r.stdout || "";
80
+ if (/ERROR.*cannot find/i.test(msg) || /找不到/.test(msg) || /未找到/.test(msg)) return { ok: true, stdout: "already disabled" };
81
+ return { ok: false, error: msg.slice(0, 800) };
82
+ }
83
+
84
+ async function winDisable() {
85
+ let r = await execAsync("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
86
+ let okSchtasks = !r.err;
87
+ let regR = await winRegDisable();
88
+ if (okSchtasks || regR.ok) return { ok: true, stdout: (r.stdout || "") + (regR.stdout || "") };
89
+ const msg = r.stderr || r.stdout || "";
90
+ if (/ERROR.*cannot find/i.test(msg) || /找不到/.test(msg)) {
91
+ if (regR.ok) return { ok: true, stdout: "already disabled" };
92
+ }
93
+ // 若两者都失败但注册表已清,也算成功
94
+ if (regR.ok) return { ok: true };
95
+ return { ok: false, error: (msg || regR.error || "").slice(0, 800) };
96
+ }
97
+
98
+ async function winStatus() {
99
+ const q = await winQuery();
100
+ if (!q.exists) return { enabled: false, detail: "未启用(无任务计划也无注册表)" };
101
+ if (q.method === "registry") {
102
+ const m = q.raw.match(/mslxdff\s+REG_SZ\s+(.+)/);
103
+ return { enabled: true, detail: `已启用 · 注册表 HKCU\\Run(登录即起,无需管理员)`, taskToRun: m ? m[1].trim() : "", raw: q.raw };
104
+ }
105
+ const m = q.raw.match(/Status:\s*(.+)/i) || q.raw.match(/状态:\s*(.+)/);
106
+ const last = q.raw.match(/Last Run Result:\s*(.+)/i) || q.raw.match(/上次运行结果:\s*(.+)/);
107
+ const tr = q.raw.match(/Task To Run:\s*(.+)/i) || q.raw.match(/要运行的任务:\s*(.+)/);
108
+ return {
109
+ enabled: true,
110
+ detail: `已启用 · ${m ? m[1].trim() : "Ready"}${last ? ` · 上次结果 ${last[1].trim()}` : ""}`,
111
+ taskToRun: tr ? tr[1].trim() : "",
112
+ raw: q.raw,
113
+ };
114
+ }
115
+
116
+ // ---------- Linux: systemd user ----------
117
+ function systemdUnitPath() {
118
+ return join(homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
119
+ }
120
+
121
+ function systemdUnitContent() {
122
+ const node = nodePath();
123
+ const script = scriptPath();
124
+ return `[Unit]
125
+ Description=mslxdff daemon — OpenCode Free proxy
126
+ After=network.target
127
+
128
+ [Service]
129
+ Type=simple
130
+ ExecStart=${node} ${script}
131
+ Restart=always
132
+ RestartSec=3
133
+
134
+ [Install]
135
+ WantedBy=default.target
136
+ `;
137
+ }
138
+
139
+ async function linuxEnable() {
140
+ const unit = systemdUnitPath();
141
+ try {
142
+ mkdirSync(dirname(unit), { recursive: true });
143
+ writeFileSync(unit, systemdUnitContent(), "utf8");
144
+ } catch (e) {
145
+ return { ok: false, error: `write unit failed: ${e.message}` };
146
+ }
147
+ let r = await execAsync("systemctl", ["--user", "daemon-reload"]);
148
+ if (r.err) return { ok: false, error: (r.stderr || r.stdout).slice(0, 500) };
149
+ r = await execAsync("systemctl", ["--user", "enable", SERVICE_NAME]);
150
+ if (r.err) return { ok: false, error: (r.stderr || r.stdout).slice(0, 500) };
151
+ // 可选立即拉起(不强求)
152
+ await execAsync("systemctl", ["--user", "start", SERVICE_NAME]);
153
+ return { ok: true, method: "systemd:user", unit };
154
+ }
155
+
156
+ async function linuxDisable() {
157
+ await execAsync("systemctl", ["--user", "disable", SERVICE_NAME]);
158
+ await execAsync("systemctl", ["--user", "stop", SERVICE_NAME]);
159
+ try { rmSync(systemdUnitPath(), { force: true }); } catch {}
160
+ await execAsync("systemctl", ["--user", "daemon-reload"]);
161
+ return { ok: true };
162
+ }
163
+
164
+ async function linuxStatus() {
165
+ const unit = systemdUnitPath();
166
+ const exists = existsSync(unit);
167
+ if (!exists) return { enabled: false, detail: "未启用(无 systemd user unit)" };
168
+ const r = await execAsync("systemctl", ["--user", "is-enabled", SERVICE_NAME]);
169
+ const enabled = !r.err && /enabled/i.test(r.stdout);
170
+ const r2 = await execAsync("systemctl", ["--user", "is-active", SERVICE_NAME]);
171
+ const active = !r2.err && /active/i.test(r2.stdout);
172
+ return { enabled, detail: `${enabled ? "已启用" : "已禁用"} · ${active ? "运行中" : "未运行"} · ${unit}`, unit };
173
+ }
174
+
175
+ // ---------- facade ----------
176
+ export async function enableAutostart() {
177
+ if (isWin()) return winEnable();
178
+ return linuxEnable();
179
+ }
180
+
181
+ export async function disableAutostart() {
182
+ if (isWin()) return winDisable();
183
+ return linuxDisable();
184
+ }
185
+
186
+ export async function getAutostartStatus() {
187
+ if (isWin()) return winStatus();
188
+ return linuxStatus();
189
+ }
190
+
191
+ export function autostartHelp() {
192
+ if (isWin()) return `Windows: schtasks 任务 "mslxdff"(ONLOGON,最高权限,当前用户登录即自启)`;
193
+ return `Linux: systemd user unit ~/.config/systemd/user/mslxdff.service`;
194
+ }
@@ -124,28 +124,9 @@ export function createChatGateway({ upstream, auto, logs, peers, maxHops, groups
124
124
  if (Number.isInteger(v) && v > 0) return Math.min(v, nonCoolingOrder.length);
125
125
  return Math.min(nonCoolingOrder.length, 5);
126
126
  })();
127
- const raceModels = nonCoolingOrder.slice(0, concLimit);
128
- evt("auto-concurrent-race", { reqId, models: raceModels, skippedFaulty: order.length - nonCoolingOrder.length, limit: concLimit });
129
- const raceStart = performance.now();
130
- const attempts = raceModels.map(async (m) => {
131
- const fwd = { ...injectReasoningContent(m, body), model: m };
132
- let r = null;
133
- try {
134
- const chatOpts = {};
135
- if (Object.keys(shareKeys).length) chatOpts.shareKeys = shareKeys;
136
- if (workbuddyUid) chatOpts.workbuddyUid = workbuddyUid;
137
- r = await upstream.chat(fwd, Object.keys(chatOpts).length ? chatOpts : undefined);
138
- } catch (err) {
139
- return { model: m, ok: false, error: errMsg(err), status: 502, timing: err?._t ?? null };
140
- }
141
- if (r && r.status >= 400) {
142
- const isAllow = r.status === 403 && r.headers?.get?.("x-mslxdff-allowlist") === "1";
143
- if (isAllow) return { model: m, ok: false, error: "allowlist", status: 403, allowlist: true };
144
- return { model: m, ok: false, error: `upstream ${r.status}`, status: r.status, res: r, timing: r._t ?? null };
145
- }
146
- if (r instanceof Error) return { model: m, ok: false, error: errMsg(r), status: 502 };
147
- return { model: m, ok: true, res: r, status: r.status, timing: r._t ?? null };
148
- });
127
+ let raceModels=nonCoolingOrder.slice(0,concLimit);
128
+ if(plugins?.length){const k=[];for(const m of raceModels){const b=await runHook(plugins,"model:beforeTry",{reqId,requested,model:m,hops});if(b.value===false||b.value?.skip)continue;k.push(m);}raceModels=k;}
129
+ if(!raceModels.length){order=order.filter(m=>!new Set(nonCoolingOrder.slice(0,concLimit)).has(m));if(!order.length){await handleExhaustedAll({res,body,lastErr:{model:requested,status:502,message:"all concurrent candidates skipped by plugin"},order:nonCoolingOrder.slice(0,concLimit),requested,handlerCtx:{...handlerCtx,reqId,startedAt},evt,logCall,mark,perf0,stages});return;}}else{evt("auto-concurrent-race",{reqId,models:raceModels,skippedFaulty:order.length-nonCoolingOrder.length,limit:concLimit});const raceStart=performance.now();const attempts=raceModels.map(async m=>{let f={...injectReasoningContent(m,body),model:m};if(plugins?.length){const u=await runHook(plugins,"upstream:request",{reqId,requested,model:m,payload:f,stream:Boolean(body.stream)});if(u.changed&&u.value?.payload) f=u.value.payload;}let r=null;try{const o={};if(Object.keys(shareKeys).length)o.shareKeys=shareKeys;if(workbuddyUid)o.workbuddyUid=workbuddyUid;r=await upstream.chat(f,Object.keys(o).length?o:undefined);}catch(e){if(plugins?.length)runHook(plugins,"upstream:response",{reqId,requested,model:m,status:null,ok:false,error:errMsg(e),timing:e?._t??null}).catch(()=>{});return{model:m,ok:false,error:errMsg(e),status:502,timing:e?._t??null};}if(plugins?.length)runHook(plugins,"upstream:response",{reqId,requested,model:m,status:r instanceof Error?null:r?.status??null,ok:!(r instanceof Error)&&r?r.status<400:false,error:r instanceof Error?errMsg(r):null,timing:r?._t??null}).catch(()=>{});if(r&&r.status>=400){const a=r.status===403&&r.headers?.get?.("x-mslxdff-allowlist")==="1";if(a)return{model:m,ok:false,error:"allowlist",status:403,allowlist:true};return{model:m,ok:false,error:`upstream ${r.status}`,status:r.status,res:r,timing:r._t??null};}if(r instanceof Error)return{model:m,ok:false,error:errMsg(r),status:502};return{model:m,ok:true,res:r,status:r.status,timing:r._t??null};});
149
130
  const results = await Promise.allSettled(attempts);
150
131
  const okList = results.map((r, i) => ({ r, i, model: raceModels[i] }))
151
132
  .filter(({ r }) => r.status === "fulfilled" && r.value?.ok)
@@ -185,10 +166,13 @@ export function createChatGateway({ upstream, auto, logs, peers, maxHops, groups
185
166
  const triedSet = new Set(raceModels);
186
167
  order = order.filter((m) => !triedSet.has(m));
187
168
  if (!order.length) {
188
- const last = { model: raceModels[0] || requested, status: 502, message: "all concurrent candidates failed" };
169
+ const failedStatuses = results.map((r) => (r.status === "fulfilled" ? r.value?.status : null)).filter((s) => Number.isInteger(s));
170
+ const lastStatus = failedStatuses[failedStatuses.length - 1] || failedStatuses[0] || 502;
171
+ const last = { model: raceModels[0] || requested, status: lastStatus, message: "all concurrent candidates failed" };
189
172
  await handleExhaustedAll({ res, body, lastErr: last, order: raceModels, requested, handlerCtx: { ...handlerCtx, reqId, startedAt }, evt, logCall, mark, perf0, stages });
190
173
  return;
191
174
  }
175
+ } // close else (raceModels not empty)
192
176
  }
193
177
  }
194
178