mslxdff 0.1.203 → 0.1.207
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/README.md +3 -3
- package/bin/mslxdff.js +1 -1
- package/docs/cli_help_mini.md +146 -0
- package/package.json +38 -38
- package/plugins/README.md +14 -14
- package/plugins/prefer-model.mjs.example +14 -14
- package/src/anthropic/canonical-json.js +55 -0
- package/src/anthropic/count-tokens.js +61 -61
- package/src/anthropic/request-body.js +183 -126
- package/src/anthropic/request-shape.js +60 -0
- package/src/anthropic/session-id.js +58 -0
- package/src/anthropic/shared.js +18 -18
- package/src/anthropic/sse-aggregate.js +56 -56
- package/src/anthropic/tool-media.js +75 -0
- package/src/anthropic/translate.js +296 -287
- package/src/anthropic/web-search.js +361 -361
- package/src/api-kind.js +31 -31
- package/src/autostart.js +288 -288
- package/src/bench/probe.js +1 -1
- package/src/bench/runner.js +1 -1
- package/src/bench/via-probe.js +1 -1
- package/src/bench/via.js +144 -144
- package/src/bench/workbuddy-bench.js +1 -1
- package/src/chat/config.js +11 -11
- package/src/chat/cooling.js +81 -81
- package/src/chat/direct.js +58 -58
- package/src/chat/index.js +6 -6
- package/src/chat/spinner.js +38 -38
- package/src/chat/sse.js +69 -69
- package/src/chat/stats.js +247 -247
- package/src/chat/store.js +48 -48
- package/src/chat/tool-handlers.js +67 -67
- package/src/chat/tools.js +1 -1
- package/src/chat/upstream.js +59 -59
- package/src/chat-pipeline/auto-race.js +158 -158
- package/src/chat-pipeline/empty-turn.js +147 -147
- package/src/chat-pipeline/engine.js +17 -17
- package/src/chat-pipeline/index.js +8 -40
- package/src/chat-pipeline/serial-trial.js +279 -283
- package/src/chat-pipeline/terminal-verdict.js +132 -147
- package/src/chooser.js +54 -54
- package/src/claude-desktop/apply.js +101 -101
- package/src/claude-desktop/context1m.js +38 -38
- package/src/claude-desktop/doctor.js +146 -146
- package/src/claude-desktop/slots.js +152 -152
- package/src/cli/bootstrap.js +1 -1
- package/src/cli/commands/claude-desktop.js +284 -284
- package/src/cli/commands/daemon.js +231 -231
- package/src/cli/commands/model/list-live.js +22 -22
- package/src/cli/commands/model/list-providers.js +76 -76
- package/src/cli/commands/model/list-render.js +77 -77
- package/src/cli/commands/model/list-sort.js +52 -52
- package/src/cli/commands/model/list-tty.js +76 -76
- package/src/cli/commands/model/list.js +166 -166
- package/src/cli/commands/model/live-models.js +24 -24
- package/src/cli/commands/model/stats.js +43 -43
- package/src/cli/commands/model/status.js +47 -47
- package/src/cli/commands/model.js +51 -51
- package/src/cli/commands/provider/add.js +108 -108
- package/src/cli/commands/provider/allowlist.js +99 -99
- package/src/cli/commands/provider/cline-free.js +99 -99
- package/src/cli/commands/provider/cline-login.js +218 -218
- package/src/cli/commands/provider/cline-quota.js +65 -65
- package/src/cli/commands/provider/codearts-login.js +45 -45
- package/src/cli/commands/provider/config.js +119 -119
- package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
- package/src/cli/commands/provider/keys-guard.js +50 -50
- package/src/cli/commands/provider/keys.js +161 -161
- package/src/cli/commands/provider/qoder-checkin.js +81 -81
- package/src/cli/commands/provider/qoder-login.js +102 -102
- package/src/cli/commands/provider/raccoon-login.js +180 -180
- package/src/cli/commands/provider/raccoon-password.js +162 -162
- package/src/cli/commands/provider/raccoon-quota.js +109 -109
- package/src/cli/commands/provider/traework-checkin.js +144 -144
- package/src/cli/commands/provider/traework-login.js +164 -164
- package/src/cli/commands/provider/workbuddy-login.js +153 -153
- package/src/cli/commands/provider/zcode-login.js +77 -77
- package/src/cli/commands/provider/zcode-quota.js +55 -55
- package/src/cli/commands/provider.js +1 -1
- package/src/cli/commands/stats.js +158 -149
- package/src/cli/commands/system.js +261 -261
- package/src/cli/commands/timezone.js +47 -47
- package/src/cli/commands/use-group.js +60 -60
- package/src/cli/commands/workbuddy.js +189 -189
- package/src/cli/format.js +141 -141
- package/src/cli/group-helpers.js +70 -70
- package/src/cli/help.js +80 -80
- package/src/cli/index.js +70 -70
- package/src/cli/interactive.js +121 -121
- package/src/cli/join-core.js +99 -99
- package/src/cli/join-wizard.js +175 -175
- package/src/cli/policy.js +136 -136
- package/src/cli/provider-row.js +126 -126
- package/src/cli/util.js +24 -24
- package/src/compat.js +53 -53
- package/src/events.js +38 -38
- package/src/free-lane.js +131 -131
- package/src/free-watcher.js +63 -63
- package/src/groups.js +262 -262
- package/src/logs.js +195 -195
- package/src/metrics.js +71 -62
- package/src/model-capabilities/context-lookup.js +65 -65
- package/src/model-capabilities/merge.js +142 -142
- package/src/model-trace.js +187 -187
- package/src/models.js +1 -1
- package/src/opencode-identity.js +72 -72
- package/src/peers.js +254 -254
- package/src/plugins.js +107 -107
- package/src/providers/AGENTS.md +49 -0
- package/src/providers/base.js +164 -164
- package/src/providers/classify.js +22 -22
- package/src/providers/cline/allowlist-sync.js +93 -93
- package/src/providers/cline/free-catalog.js +71 -71
- package/src/providers/cline/headers.js +37 -37
- package/src/providers/cline/index.js +130 -130
- package/src/providers/cline/models.js +150 -150
- package/src/providers/cline/usage.js +274 -274
- package/src/providers/codearts/auth-pool.js +148 -148
- package/src/providers/codearts/concurrency.js +34 -34
- package/src/providers/codearts/const.js +65 -65
- package/src/providers/codearts/dpop.js +95 -95
- package/src/providers/codearts/index.js +175 -175
- package/src/providers/codearts/login.js +214 -214
- package/src/providers/codearts/models.js +182 -182
- package/src/providers/codearts/sign.js +96 -96
- package/src/providers/codearts/sse.js +204 -204
- package/src/providers/codearts/stream.js +209 -209
- package/src/providers/codearts/sts.js +124 -124
- package/src/providers/codearts.js +1 -1
- package/src/providers/dispatcher.js +146 -146
- package/src/providers/generic.js +156 -156
- package/src/providers/globalqwenwork/account-store.js +141 -141
- package/src/providers/globalqwenwork/constants.js +73 -73
- package/src/providers/globalqwenwork/crypto.js +2 -2
- package/src/providers/globalqwenwork/http.js +2 -2
- package/src/providers/globalqwenwork/index.js +331 -331
- package/src/providers/globalqwenwork/payload.js +147 -147
- package/src/providers/globalqwenwork/rsa.js +2 -2
- package/src/providers/globalqwenwork/sse.js +2 -2
- package/src/providers/globalqwenwork/stream.js +2 -2
- package/src/providers/globalqwenwork.js +1 -1
- package/src/providers/globalworkbuddy/account-store.js +198 -198
- package/src/providers/globalworkbuddy/auth.js +97 -97
- package/src/providers/globalworkbuddy/catalog.js +138 -138
- package/src/providers/globalworkbuddy/constants.js +73 -73
- package/src/providers/globalworkbuddy/device-auth.js +120 -120
- package/src/providers/globalworkbuddy/errors.js +98 -98
- package/src/providers/globalworkbuddy/headers.js +89 -89
- package/src/providers/globalworkbuddy/models.js +96 -96
- package/src/providers/globalworkbuddy/payload.js +80 -80
- package/src/providers/index.js +5 -5
- package/src/providers/model-id.js +172 -172
- package/src/providers/opencode.js +19 -19
- package/src/providers/openrouter.js +31 -31
- package/src/providers/qoder/account-store.js +106 -106
- package/src/providers/qoder/aggregate.js +60 -60
- package/src/providers/qoder/baseprompt.json +591 -591
- package/src/providers/qoder/checkin.js +160 -160
- package/src/providers/qoder/constants.js +44 -44
- package/src/providers/qoder/encode.js +41 -41
- package/src/providers/qoder/fingerprint.js +38 -38
- package/src/providers/qoder/models.js +77 -77
- package/src/providers/qoder/oauth.js +84 -84
- package/src/providers/qoder/payload.js +162 -162
- package/src/providers/qoder/request.js +27 -27
- package/src/providers/qoder/session.js +134 -134
- package/src/providers/qoder/sticky.js +38 -38
- package/src/providers/qoder/stream.js +116 -116
- package/src/providers/qoder.js +1 -1
- package/src/providers/raccoon/account-store.js +216 -216
- package/src/providers/raccoon/auth.js +67 -67
- package/src/providers/raccoon/chat.js +228 -228
- package/src/providers/raccoon/const.js +180 -180
- package/src/providers/raccoon/credits.js +185 -185
- package/src/providers/raccoon/crypto.js +43 -43
- package/src/providers/raccoon/envelope.js +31 -31
- package/src/providers/raccoon/headers.js +45 -45
- package/src/providers/raccoon/index.js +216 -216
- package/src/providers/raccoon/login.js +279 -279
- package/src/providers/raccoon/models.js +160 -160
- package/src/providers/raccoon/password-login.js +102 -102
- package/src/providers/raccoon/qr-codewords.js +115 -115
- package/src/providers/raccoon/qr.js +236 -236
- package/src/providers/raccoon/sse.js +135 -135
- package/src/providers/responses-channel.js +120 -120
- package/src/providers/share-keys.js +53 -53
- package/src/providers/traework/account-store.js +234 -234
- package/src/providers/traework/chat.js +217 -217
- package/src/providers/traework/checkin.js +77 -77
- package/src/providers/traework/constants.js +41 -41
- package/src/providers/traework/errors.js +52 -52
- package/src/providers/traework/headers.js +79 -79
- package/src/providers/traework/index.js +132 -132
- package/src/providers/traework/market-identity.js +116 -116
- package/src/providers/traework/models.js +98 -98
- package/src/providers/traework/payload.js +86 -86
- package/src/providers/traework/sse.js +184 -184
- package/src/providers/traework/token.js +52 -52
- package/src/providers/traework.js +2 -2
- package/src/providers/workbuddy/account-store.js +186 -186
- package/src/providers/workbuddy/auth.js +158 -158
- package/src/providers/workbuddy/balance.js +86 -86
- package/src/providers/workbuddy/cat-travel.js +125 -125
- package/src/providers/workbuddy/checkin.js +75 -75
- package/src/providers/workbuddy/desktop-info.js +78 -78
- package/src/providers/workbuddy/growth-api.js +67 -67
- package/src/providers/workbuddy/growth-plans.js +63 -63
- package/src/providers/workbuddy/growth.js +181 -181
- package/src/providers/workbuddy/index.js +217 -217
- package/src/providers/workbuddy/models.js +126 -126
- package/src/providers/workbuddy/payload.js +141 -141
- package/src/providers/workbuddy/rotation-log.js +83 -83
- package/src/providers/workbuddy/sanitize-tools.js +61 -61
- package/src/providers/workbuddy/sdk-chat.js +26 -26
- package/src/providers/workbuddy-balance.js +78 -78
- package/src/providers/zcode/account-store.js +129 -129
- package/src/providers/zcode/auth.js +28 -28
- package/src/providers/zcode/chat.js +179 -179
- package/src/providers/zcode/const.js +54 -54
- package/src/providers/zcode/context-shape.js +162 -162
- package/src/providers/zcode/headers.js +93 -93
- package/src/providers/zcode/index.js +124 -124
- package/src/providers/zcode/models.js +65 -65
- package/src/providers/zcode/oauth.js +120 -120
- package/src/providers/zcode/quota.js +176 -176
- package/src/providers/zcode/sse.js +196 -196
- package/src/readline-compat.js +25 -25
- package/src/responses/translate.js +311 -311
- package/src/routes/AGENTS.md +37 -0
- package/src/routes/chat/broadband-handler.js +72 -72
- package/src/routes/chat/empty-turn-report.js +31 -31
- package/src/routes/chat/exhausted-handler.js +87 -87
- package/src/routes/chat/hedge-handler.js +130 -130
- package/src/routes/chat/index.js +1 -1
- package/src/routes/chat/local-handler.js +63 -63
- package/src/routes/chat/peer-handler.js +81 -81
- package/src/routes/chat.js +3 -3
- package/src/routes/fallback.js +88 -88
- package/src/routes/groups-relay.js +214 -214
- package/src/routes/groups.js +122 -122
- package/src/routes/helpers.js +93 -83
- package/src/routes/index.js +147 -147
- package/src/routes/messages-route.js +375 -365
- package/src/routes/peers.js +10 -2
- package/src/routes/relay-queue.js +181 -181
- package/src/routes/relay.js +60 -60
- package/src/routes/responses-route.js +213 -213
- package/src/routes/stream-body.js +84 -84
- package/src/routes/stream-hold.js +337 -337
- package/src/routes/stream-scan.js +366 -366
- package/src/routes.js +3 -3
- package/src/runtime/auto-update.js +113 -113
- package/src/runtime/broadband-stream.js +79 -79
- package/src/runtime/broadband.js +103 -103
- package/src/runtime/group-sync.js +81 -81
- package/src/runtime/keep-warm.js +120 -120
- package/src/runtime/lifecycle-forensics.js +116 -116
- package/src/runtime/lifecycle-log.js +87 -87
- package/src/runtime/log-stamp.js +73 -73
- package/src/runtime/proc-info.js +137 -137
- package/src/runtime/qoder-checkin.js +93 -93
- package/src/runtime/raccoon-checkin.js +126 -126
- package/src/runtime/server-lifecycle.js +189 -189
- package/src/runtime/traework-checkin.js +118 -118
- package/src/runtime/workbuddy-checkin.js +120 -120
- package/src/runtime/workbuddy-growth.js +133 -133
- package/src/state/facade.js +58 -58
- package/src/state/memory.js +162 -162
- package/src/state/merge.js +25 -25
- package/src/state/migrations/cline-unify.js +94 -94
- package/src/state/migrations.js +33 -33
- package/src/state/persist.js +42 -42
- package/src/state/provider-config.js +142 -142
- package/src/state/schemas/group.js +31 -31
- package/src/state/schemas/model.js +94 -94
- package/src/state/schemas/peer.js +31 -31
- package/src/state/schemas/port.js +10 -10
- package/src/state/schemas/provider.js +202 -202
- package/src/state/schemas/timezone.js +70 -70
- package/src/state/schemas/token.js +48 -48
- package/src/state/schemas/use-group.js +102 -102
- package/src/state/store.js +176 -176
- package/src/state.js +1 -1
- package/src/sync-claude-desktop.js +234 -234
- package/src/sync-claude.js +152 -152
- package/src/sync-codex.js +116 -116
- package/src/sync-workbuddy.js +158 -158
- package/src/talk-full-redact.js +129 -129
- package/src/talk-full.js +131 -131
- package/src/talk-log.js +257 -257
- package/src/text-tokens.js +52 -52
- package/src/thinking-effort.js +67 -67
- package/src/time.js +106 -106
- package/src/timeline.js +26 -26
- package/src/transport/index.js +1 -1
- package/src/upstream-engine/index.js +74 -74
- package/src/upstream-engine/mode.js +15 -15
- package/src/upstream-engine/sdk/attempt.js +221 -221
- package/src/upstream-engine/sdk/chat.js +28 -28
- package/src/upstream-engine/sdk/convert.js +192 -192
- package/src/upstream-engine/sdk/diagnose.js +62 -62
- package/src/upstream-engine/sdk/dispatch.js +55 -55
- package/src/upstream-engine/sdk/responses.js +176 -176
- package/src/upstream-engine/sdk/sse.js +166 -166
- package/src/upstream-responses.js +305 -305
- package/src/usage/cache-fields.js +58 -0
- package/src/usage/record.js +109 -106
- package/src/usage/report.js +209 -195
- package/src/v6pool/claim.js +157 -157
- package/src/v6pool/config.js +65 -65
- package/src/v6pool/detect.js +130 -130
- package/src/v6pool/index.js +152 -152
- package/src/v6pool/prefix.js +140 -140
- package/src/v6pool/probe.js +40 -40
- package/src/v6pool/report.js +104 -104
- package/src/v6pool/rotate.js +263 -263
- package/src/warp-pool/config.js +74 -74
- package/src/warp-pool/health.js +106 -106
- package/src/warp-pool/identity.js +141 -141
- package/src/warp-pool/index.js +358 -358
- package/src/warp-pool/keys.js +29 -29
- package/src/warp-pool/nodes.js +95 -95
- package/src/warp-pool/ports.js +160 -160
- package/src/warp-pool/register.js +113 -113
- package/src/warp-pool/report.js +232 -232
- package/src/warp-pool/revive.js +168 -168
- package/src/warp-pool/rotate.js +214 -214
- package/src/warp-pool/strategy.js +43 -43
- package/src/warp-pool/verdict.js +69 -69
- package/src/warp-pool/wireproxy.js +162 -162
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
import { startServer, resolvePort } from "../server.js";
|
|
2
|
-
import { createRouter } from "../routes.js";
|
|
3
|
-
import { createEventBus } from "../events.js";
|
|
4
|
-
import { runHook } from "../plugins.js";
|
|
5
|
-
import { effectiveHost, maxHopsValue } from "../cli/policy.js";
|
|
6
|
-
import { fmtEvent } from "../cli/format.js";
|
|
7
|
-
import { writePid } from "../daemon.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 服务生命周期 — router 装配 → server 创建/ready-EADDRINUSE 自愈 →
|
|
11
|
-
* plugin 事件 → preheat → pid/监听日志 → autostart。返回 { srv, bus, router }。
|
|
12
|
-
*/
|
|
13
|
-
export async function startServerLifecycle({ VERSION, token, created, upstream, models, auto, peers, groups, bans, loadedPlugins, baseUrl, logs }) {
|
|
14
|
-
const bus = createEventBus();
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const { loadModelAliases } = await import("../providers/model-id.js");
|
|
18
|
-
loadModelAliases();
|
|
19
|
-
} catch {}
|
|
20
|
-
|
|
21
|
-
const router = createRouter({ token, upstream, models, auto, logs, peers, maxHops: maxHopsValue(), groups, bans, bus, plugins: loadedPlugins, version: VERSION });
|
|
22
|
-
const listenHost = effectiveHost();
|
|
23
|
-
const isDebug = process.env.MSLXDFF_DEBUG === "1";
|
|
24
|
-
const srv = startServer({
|
|
25
|
-
router,
|
|
26
|
-
signals: !isDebug,
|
|
27
|
-
host: listenHost,
|
|
28
|
-
onBeforeClose: loadedPlugins.length
|
|
29
|
-
? () => runHook(loadedPlugins, "server:stop", { version: VERSION }).then(() => {})
|
|
30
|
-
: undefined,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (isDebug) {
|
|
34
|
-
bus.subscribe((e) => {
|
|
35
|
-
try {
|
|
36
|
-
console.log(fmtEvent(e));
|
|
37
|
-
} catch {}
|
|
38
|
-
});
|
|
39
|
-
const { startDaemon: sd } = await import("../daemon.js");
|
|
40
|
-
const restore2 = async () => {
|
|
41
|
-
console.log("\n[debug] restoring background daemon...");
|
|
42
|
-
try { await srv.close(); } catch {} // 先释放端口,避免恢复的 daemon 抢端口互杀
|
|
43
|
-
if (process.platform === "linux") {
|
|
44
|
-
try {
|
|
45
|
-
const { execFile } = await import("node:child_process");
|
|
46
|
-
const ok = await new Promise((res) => execFile("systemctl", ["--user", "start", "mslxdff"], { windowsHide: true, timeout: 8000 }, (e) => res(!e)));
|
|
47
|
-
if (ok) { console.log("[debug] systemd user service restarted (mslxdff)"); setTimeout(() => process.exit(0), 300); return; }
|
|
48
|
-
} catch {}
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const restoredPid = sd([]);
|
|
52
|
-
console.log(`[debug] daemon restored (pid ${restoredPid})`);
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error(`[debug] could not restore daemon: ${err.message}`);
|
|
55
|
-
}
|
|
56
|
-
setTimeout(() => process.exit(0), 300);
|
|
57
|
-
};
|
|
58
|
-
process.on("SIGINT", restore2);
|
|
59
|
-
process.on("SIGTERM", restore2);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Robust ready: if EADDRINUSE (bare daemon still holds port), kill holders and retry once
|
|
63
|
-
try {
|
|
64
|
-
await srv.ready();
|
|
65
|
-
} catch (err) {
|
|
66
|
-
const msg = String(err?.message || err);
|
|
67
|
-
const code = err?.code || "";
|
|
68
|
-
if (code === "EADDRINUSE" || msg.includes("EADDRINUSE")) {
|
|
69
|
-
console.log(`[lifecycle] EADDRINUSE on port ${resolvePort()} — freeing stale holder and retrying...`);
|
|
70
|
-
try {
|
|
71
|
-
const { execFile } = await import("node:child_process");
|
|
72
|
-
const execAsync2 = (f, a) => new Promise((res) => execFile(f, a, { windowsHide: true, timeout: 4000 }, (e, so, se) => res({ e, so: String(so||""), se: String(se||"") })));
|
|
73
|
-
const port = resolvePort();
|
|
74
|
-
// kill via ss parse (same as autostart)
|
|
75
|
-
const ss1 = await execAsync2("ss", ["-lptn", `sport = :${port}`]);
|
|
76
|
-
const out = ss1.so || "";
|
|
77
|
-
const pids = new Set();
|
|
78
|
-
let m;
|
|
79
|
-
const re = /pid=(\d+)/g;
|
|
80
|
-
while ((m = re.exec(out))) pids.add(Number(m[1]));
|
|
81
|
-
if (!pids.size) {
|
|
82
|
-
const ss2 = await execAsync2("ss", ["-lptn"]);
|
|
83
|
-
for (const line of (ss2.so||"").split("\n")) {
|
|
84
|
-
if (!line.includes(`:${port}`)) continue;
|
|
85
|
-
let m2; const re2 = /pid=(\d+)/g;
|
|
86
|
-
while ((m2 = re2.exec(line))) pids.add(Number(m2[1]));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (pids.size) console.log(`[lifecycle] killing stale holder(s): ${[...pids].join(",")} (self=${process.pid})`);
|
|
90
|
-
else console.log(`[lifecycle] no holder pid discoverable (ss/fuser unavailable on this platform?) — retry may fail`);
|
|
91
|
-
for (const p of pids) { if (p !== process.pid) try { process.kill(p, "SIGTERM"); } catch {} }
|
|
92
|
-
if (pids.size) await new Promise((r2) => setTimeout(r2, 600));
|
|
93
|
-
for (const p of pids) try { const { isPidAlive } = await import("../daemon.js"); if (isPidAlive(p)) process.kill(p, "SIGKILL"); } catch {}
|
|
94
|
-
try { await execAsync2("fuser", ["-k", `${port}/tcp`]); } catch {}
|
|
95
|
-
for (let i=0;i<10;i++) {
|
|
96
|
-
const chk = await execAsync2("ss", ["-ltn"]);
|
|
97
|
-
if (!chk.so.includes(`:${port}`)) break;
|
|
98
|
-
await new Promise((r2)=>setTimeout(r2,200));
|
|
99
|
-
}
|
|
100
|
-
} catch {}
|
|
101
|
-
await srv.ready();
|
|
102
|
-
} else throw err;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (loadedPlugins.length) {
|
|
106
|
-
runHook(loadedPlugins, "server:start", { port: srv.server.address()?.port, host: listenHost, version: VERSION }).catch(() => {});
|
|
107
|
-
const eventPlugins = loadedPlugins.filter((p) => typeof p.onEvent === "function");
|
|
108
|
-
if (eventPlugins.length) {
|
|
109
|
-
bus.subscribe((e) => {
|
|
110
|
-
for (const p of eventPlugins) {
|
|
111
|
-
try { p.onEvent(e); } catch {}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
setTimeout(() => {
|
|
118
|
-
upstream.preheat().then((r) => {
|
|
119
|
-
const entry = { ts: Date.now(), type: "upstream-preheat", ...r, baseUrl };
|
|
120
|
-
try { bus.emit(entry); } catch {}
|
|
121
|
-
try { logs.appendEvent(entry); } catch {}
|
|
122
|
-
if (r.skipped) console.log(`[preheat] skipped (MSLXDFF_PREHEAT disabled)`);
|
|
123
|
-
else if (Array.isArray(r.results)) {
|
|
124
|
-
const fails = r.results.filter((x) => !x.ok).map((x) => x.id).join(",");
|
|
125
|
-
console.log(`[preheat] custom ${r.okCount}/${r.total} ok${fails ? ` fail:${fails}` : ""}`);
|
|
126
|
-
}
|
|
127
|
-
else if (r.ok) console.log(`[preheat] opencode models ok ${r.status} ${r.ms}ms`);
|
|
128
|
-
else console.log(`[preheat] opencode models failed ${r.error || r.status || ""} ${r.ms || 0}ms`);
|
|
129
|
-
}).catch(() => {});
|
|
130
|
-
// cline 自检已并入 dispatcher.preheat(定制名单含 cline,走 checkFreeUpdates 带快照 diff),此处不再单独调
|
|
131
|
-
}, 100).unref?.();
|
|
132
|
-
|
|
133
|
-
// responses 模型判定改元数据驱动(models.dev provider.npm):就绪后注入,每小时重查使新模型自动识别
|
|
134
|
-
void (async () => {
|
|
135
|
-
try {
|
|
136
|
-
const { globalCapabilities } = await import("../model-capabilities/index.js");
|
|
137
|
-
const { setResponsesNpmIndex } = await import("../upstream-responses.js");
|
|
138
|
-
const svc = globalCapabilities();
|
|
139
|
-
const apply = () => setResponsesNpmIndex(svc.npmIndex());
|
|
140
|
-
await svc.ready();
|
|
141
|
-
apply();
|
|
142
|
-
const entry = { ts: Date.now(), type: "responses-npm-index", models: svc.npmIndex().size };
|
|
143
|
-
try { bus.emit(entry); } catch {}
|
|
144
|
-
try { logs.appendEvent(entry); } catch {}
|
|
145
|
-
setInterval(() => { svc.ready().then(apply).catch(() => {}); }, 60 * 60 * 1000).unref?.();
|
|
146
|
-
} catch (e) {
|
|
147
|
-
try { logs.appendEvent({ ts: Date.now(), type: "responses-npm-index-failed", error: String(e?.message || e).slice(0, 200) }); } catch {}
|
|
148
|
-
}
|
|
149
|
-
})();
|
|
150
|
-
|
|
151
|
-
models.startAutoRefresh();
|
|
152
|
-
if (process.env.MSLXDFF_DAEMON) {
|
|
153
|
-
writePid(process.pid, VERSION);
|
|
154
|
-
}
|
|
155
|
-
const addr = srv.server.address();
|
|
156
|
-
const host = addr.address === "0.0.0.0" || addr.address === "::" ? "localhost" : addr.address;
|
|
157
|
-
console.log(`mslxdff v${VERSION} listening on http://${host}:${addr.port} (pid ${process.pid})`);
|
|
158
|
-
if (created) {
|
|
159
|
-
console.log(`auth token: ${token}`);
|
|
160
|
-
}
|
|
161
|
-
console.log(`endpoint: http://${host}:${addr.port}/v1`);
|
|
162
|
-
try {
|
|
163
|
-
const { hedgeDelayMs } = await import("../routes/hedge.js");
|
|
164
|
-
const hd = hedgeDelayMs();
|
|
165
|
-
console.log(`hedge: ${hd ? `${hd}ms` : "off"} (MSLXDFF_HEDGE_DELAY_MS)`);
|
|
166
|
-
} catch {}
|
|
167
|
-
|
|
168
|
-
// best-effort: ensure autostart on Linux (so daemon survives reboot/SSH disconnect without manual cmd)
|
|
169
|
-
if (process.platform === "linux" && !process.env.MSLXDFF_NO_AUTOSTART) {
|
|
170
|
-
setTimeout(async () => {
|
|
171
|
-
try {
|
|
172
|
-
const { getAutostartStatus, enableAutostart } = await import("../autostart.js");
|
|
173
|
-
const st = await getAutostartStatus();
|
|
174
|
-
if (!st.enabled) {
|
|
175
|
-
const r = await enableAutostart();
|
|
176
|
-
if (r.ok) {
|
|
177
|
-
console.log(`autostart auto-enabled: ${r.method}${r.linger ? ` linger=${r.linger}` : ""}`);
|
|
178
|
-
try { bus?.emit({ ts: Date.now(), type: "autostart-auto-enabled", method: r.method }); } catch {}
|
|
179
|
-
try { logs.appendEvent({ ts: Date.now(), type: "autostart-auto-enabled", method: r.method }); } catch {}
|
|
180
|
-
} else {
|
|
181
|
-
console.log(`autostart auto-enable failed: ${r.error || "unknown"} (run mslxdff -enable-autostart manually)`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
} catch {}
|
|
185
|
-
}, 2500).unref?.();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return { srv, bus, router };
|
|
189
|
-
}
|
|
1
|
+
import { startServer, resolvePort } from "../server.js";
|
|
2
|
+
import { createRouter } from "../routes.js";
|
|
3
|
+
import { createEventBus } from "../events.js";
|
|
4
|
+
import { runHook } from "../plugins.js";
|
|
5
|
+
import { effectiveHost, maxHopsValue } from "../cli/policy.js";
|
|
6
|
+
import { fmtEvent } from "../cli/format.js";
|
|
7
|
+
import { writePid } from "../daemon.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 服务生命周期 — router 装配 → server 创建/ready-EADDRINUSE 自愈 →
|
|
11
|
+
* plugin 事件 → preheat → pid/监听日志 → autostart。返回 { srv, bus, router }。
|
|
12
|
+
*/
|
|
13
|
+
export async function startServerLifecycle({ VERSION, token, created, upstream, models, auto, peers, groups, bans, loadedPlugins, baseUrl, logs }) {
|
|
14
|
+
const bus = createEventBus();
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const { loadModelAliases } = await import("../providers/model-id.js");
|
|
18
|
+
loadModelAliases();
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
const router = createRouter({ token, upstream, models, auto, logs, peers, maxHops: maxHopsValue(), groups, bans, bus, plugins: loadedPlugins, version: VERSION });
|
|
22
|
+
const listenHost = effectiveHost();
|
|
23
|
+
const isDebug = process.env.MSLXDFF_DEBUG === "1";
|
|
24
|
+
const srv = startServer({
|
|
25
|
+
router,
|
|
26
|
+
signals: !isDebug,
|
|
27
|
+
host: listenHost,
|
|
28
|
+
onBeforeClose: loadedPlugins.length
|
|
29
|
+
? () => runHook(loadedPlugins, "server:stop", { version: VERSION }).then(() => {})
|
|
30
|
+
: undefined,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (isDebug) {
|
|
34
|
+
bus.subscribe((e) => {
|
|
35
|
+
try {
|
|
36
|
+
console.log(fmtEvent(e));
|
|
37
|
+
} catch {}
|
|
38
|
+
});
|
|
39
|
+
const { startDaemon: sd } = await import("../daemon.js");
|
|
40
|
+
const restore2 = async () => {
|
|
41
|
+
console.log("\n[debug] restoring background daemon...");
|
|
42
|
+
try { await srv.close(); } catch {} // 先释放端口,避免恢复的 daemon 抢端口互杀
|
|
43
|
+
if (process.platform === "linux") {
|
|
44
|
+
try {
|
|
45
|
+
const { execFile } = await import("node:child_process");
|
|
46
|
+
const ok = await new Promise((res) => execFile("systemctl", ["--user", "start", "mslxdff"], { windowsHide: true, timeout: 8000 }, (e) => res(!e)));
|
|
47
|
+
if (ok) { console.log("[debug] systemd user service restarted (mslxdff)"); setTimeout(() => process.exit(0), 300); return; }
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const restoredPid = sd([]);
|
|
52
|
+
console.log(`[debug] daemon restored (pid ${restoredPid})`);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(`[debug] could not restore daemon: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
setTimeout(() => process.exit(0), 300);
|
|
57
|
+
};
|
|
58
|
+
process.on("SIGINT", restore2);
|
|
59
|
+
process.on("SIGTERM", restore2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Robust ready: if EADDRINUSE (bare daemon still holds port), kill holders and retry once
|
|
63
|
+
try {
|
|
64
|
+
await srv.ready();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const msg = String(err?.message || err);
|
|
67
|
+
const code = err?.code || "";
|
|
68
|
+
if (code === "EADDRINUSE" || msg.includes("EADDRINUSE")) {
|
|
69
|
+
console.log(`[lifecycle] EADDRINUSE on port ${resolvePort()} — freeing stale holder and retrying...`);
|
|
70
|
+
try {
|
|
71
|
+
const { execFile } = await import("node:child_process");
|
|
72
|
+
const execAsync2 = (f, a) => new Promise((res) => execFile(f, a, { windowsHide: true, timeout: 4000 }, (e, so, se) => res({ e, so: String(so||""), se: String(se||"") })));
|
|
73
|
+
const port = resolvePort();
|
|
74
|
+
// kill via ss parse (same as autostart)
|
|
75
|
+
const ss1 = await execAsync2("ss", ["-lptn", `sport = :${port}`]);
|
|
76
|
+
const out = ss1.so || "";
|
|
77
|
+
const pids = new Set();
|
|
78
|
+
let m;
|
|
79
|
+
const re = /pid=(\d+)/g;
|
|
80
|
+
while ((m = re.exec(out))) pids.add(Number(m[1]));
|
|
81
|
+
if (!pids.size) {
|
|
82
|
+
const ss2 = await execAsync2("ss", ["-lptn"]);
|
|
83
|
+
for (const line of (ss2.so||"").split("\n")) {
|
|
84
|
+
if (!line.includes(`:${port}`)) continue;
|
|
85
|
+
let m2; const re2 = /pid=(\d+)/g;
|
|
86
|
+
while ((m2 = re2.exec(line))) pids.add(Number(m2[1]));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (pids.size) console.log(`[lifecycle] killing stale holder(s): ${[...pids].join(",")} (self=${process.pid})`);
|
|
90
|
+
else console.log(`[lifecycle] no holder pid discoverable (ss/fuser unavailable on this platform?) — retry may fail`);
|
|
91
|
+
for (const p of pids) { if (p !== process.pid) try { process.kill(p, "SIGTERM"); } catch {} }
|
|
92
|
+
if (pids.size) await new Promise((r2) => setTimeout(r2, 600));
|
|
93
|
+
for (const p of pids) try { const { isPidAlive } = await import("../daemon.js"); if (isPidAlive(p)) process.kill(p, "SIGKILL"); } catch {}
|
|
94
|
+
try { await execAsync2("fuser", ["-k", `${port}/tcp`]); } catch {}
|
|
95
|
+
for (let i=0;i<10;i++) {
|
|
96
|
+
const chk = await execAsync2("ss", ["-ltn"]);
|
|
97
|
+
if (!chk.so.includes(`:${port}`)) break;
|
|
98
|
+
await new Promise((r2)=>setTimeout(r2,200));
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
await srv.ready();
|
|
102
|
+
} else throw err;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (loadedPlugins.length) {
|
|
106
|
+
runHook(loadedPlugins, "server:start", { port: srv.server.address()?.port, host: listenHost, version: VERSION }).catch(() => {});
|
|
107
|
+
const eventPlugins = loadedPlugins.filter((p) => typeof p.onEvent === "function");
|
|
108
|
+
if (eventPlugins.length) {
|
|
109
|
+
bus.subscribe((e) => {
|
|
110
|
+
for (const p of eventPlugins) {
|
|
111
|
+
try { p.onEvent(e); } catch {}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
upstream.preheat().then((r) => {
|
|
119
|
+
const entry = { ts: Date.now(), type: "upstream-preheat", ...r, baseUrl };
|
|
120
|
+
try { bus.emit(entry); } catch {}
|
|
121
|
+
try { logs.appendEvent(entry); } catch {}
|
|
122
|
+
if (r.skipped) console.log(`[preheat] skipped (MSLXDFF_PREHEAT disabled)`);
|
|
123
|
+
else if (Array.isArray(r.results)) {
|
|
124
|
+
const fails = r.results.filter((x) => !x.ok).map((x) => x.id).join(",");
|
|
125
|
+
console.log(`[preheat] custom ${r.okCount}/${r.total} ok${fails ? ` fail:${fails}` : ""}`);
|
|
126
|
+
}
|
|
127
|
+
else if (r.ok) console.log(`[preheat] opencode models ok ${r.status} ${r.ms}ms`);
|
|
128
|
+
else console.log(`[preheat] opencode models failed ${r.error || r.status || ""} ${r.ms || 0}ms`);
|
|
129
|
+
}).catch(() => {});
|
|
130
|
+
// cline 自检已并入 dispatcher.preheat(定制名单含 cline,走 checkFreeUpdates 带快照 diff),此处不再单独调
|
|
131
|
+
}, 100).unref?.();
|
|
132
|
+
|
|
133
|
+
// responses 模型判定改元数据驱动(models.dev provider.npm):就绪后注入,每小时重查使新模型自动识别
|
|
134
|
+
void (async () => {
|
|
135
|
+
try {
|
|
136
|
+
const { globalCapabilities } = await import("../model-capabilities/index.js");
|
|
137
|
+
const { setResponsesNpmIndex } = await import("../upstream-responses.js");
|
|
138
|
+
const svc = globalCapabilities();
|
|
139
|
+
const apply = () => setResponsesNpmIndex(svc.npmIndex());
|
|
140
|
+
await svc.ready();
|
|
141
|
+
apply();
|
|
142
|
+
const entry = { ts: Date.now(), type: "responses-npm-index", models: svc.npmIndex().size };
|
|
143
|
+
try { bus.emit(entry); } catch {}
|
|
144
|
+
try { logs.appendEvent(entry); } catch {}
|
|
145
|
+
setInterval(() => { svc.ready().then(apply).catch(() => {}); }, 60 * 60 * 1000).unref?.();
|
|
146
|
+
} catch (e) {
|
|
147
|
+
try { logs.appendEvent({ ts: Date.now(), type: "responses-npm-index-failed", error: String(e?.message || e).slice(0, 200) }); } catch {}
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
150
|
+
|
|
151
|
+
models.startAutoRefresh();
|
|
152
|
+
if (process.env.MSLXDFF_DAEMON) {
|
|
153
|
+
writePid(process.pid, VERSION);
|
|
154
|
+
}
|
|
155
|
+
const addr = srv.server.address();
|
|
156
|
+
const host = addr.address === "0.0.0.0" || addr.address === "::" ? "localhost" : addr.address;
|
|
157
|
+
console.log(`mslxdff v${VERSION} listening on http://${host}:${addr.port} (pid ${process.pid})`);
|
|
158
|
+
if (created) {
|
|
159
|
+
console.log(`auth token: ${token}`);
|
|
160
|
+
}
|
|
161
|
+
console.log(`endpoint: http://${host}:${addr.port}/v1`);
|
|
162
|
+
try {
|
|
163
|
+
const { hedgeDelayMs } = await import("../routes/hedge.js");
|
|
164
|
+
const hd = hedgeDelayMs();
|
|
165
|
+
console.log(`hedge: ${hd ? `${hd}ms` : "off"} (MSLXDFF_HEDGE_DELAY_MS)`);
|
|
166
|
+
} catch {}
|
|
167
|
+
|
|
168
|
+
// best-effort: ensure autostart on Linux (so daemon survives reboot/SSH disconnect without manual cmd)
|
|
169
|
+
if (process.platform === "linux" && !process.env.MSLXDFF_NO_AUTOSTART) {
|
|
170
|
+
setTimeout(async () => {
|
|
171
|
+
try {
|
|
172
|
+
const { getAutostartStatus, enableAutostart } = await import("../autostart.js");
|
|
173
|
+
const st = await getAutostartStatus();
|
|
174
|
+
if (!st.enabled) {
|
|
175
|
+
const r = await enableAutostart();
|
|
176
|
+
if (r.ok) {
|
|
177
|
+
console.log(`autostart auto-enabled: ${r.method}${r.linger ? ` linger=${r.linger}` : ""}`);
|
|
178
|
+
try { bus?.emit({ ts: Date.now(), type: "autostart-auto-enabled", method: r.method }); } catch {}
|
|
179
|
+
try { logs.appendEvent({ ts: Date.now(), type: "autostart-auto-enabled", method: r.method }); } catch {}
|
|
180
|
+
} else {
|
|
181
|
+
console.log(`autostart auto-enable failed: ${r.error || "unknown"} (run mslxdff -enable-autostart manually)`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch {}
|
|
185
|
+
}, 2500).unref?.();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { srv, bus, router };
|
|
189
|
+
}
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* traework 每日自动签到(daemon 内置),仿 src/runtime/workbuddy-checkin.js。
|
|
3
|
-
* 开关:MSLXDFF_TRAEWORK_CHECKIN=0 关闭(默认开);时间:MSLXDFF_TRAEWORK_CHECKIN_HOUR(默认 9 点)。
|
|
4
|
-
* 多账号全签 + 启动补签 + 每日定时。
|
|
5
|
-
*/
|
|
6
|
-
import { compatFetch } from "../compat.js";
|
|
7
|
-
import { todayKey, nextRunDelayMs, shouldCatchUp } from "./workbuddy-checkin.js";
|
|
8
|
-
|
|
9
|
-
export function isCheckinEnabled(env = process.env) {
|
|
10
|
-
const v = String(env.MSLXDFF_TRAEWORK_CHECKIN ?? "1").trim().toLowerCase();
|
|
11
|
-
return !["0", "false", "off", "no"].includes(v);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function getCheckinHour(env = process.env) {
|
|
15
|
-
const h = Number(env.MSLXDFF_TRAEWORK_CHECKIN_HOUR);
|
|
16
|
-
return Number.isInteger(h) && h >= 0 && h <= 23 ? h : 9;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function setupTraeworkCheckin({ bus, logs, deps = {} } = {}) {
|
|
20
|
-
function emit(type, data = {}) {
|
|
21
|
-
const entry = { ts: Date.now(), type, ...data };
|
|
22
|
-
try { bus?.emit(entry); } catch {}
|
|
23
|
-
try { logs?.appendEvent?.(entry); } catch {}
|
|
24
|
-
try { console.log(`[traework-checkin] ${type} ${JSON.stringify(data)}`.slice(0, 500)); } catch {}
|
|
25
|
-
}
|
|
26
|
-
if (!isCheckinEnabled()) {
|
|
27
|
-
console.log("[traework-checkin] disabled (set MSLXDFF_TRAEWORK_CHECKIN=1 to enable)");
|
|
28
|
-
emit("traework-checkin-disabled");
|
|
29
|
-
return { enabled: false };
|
|
30
|
-
}
|
|
31
|
-
const hour = getCheckinHour();
|
|
32
|
-
console.log(`[traework-checkin] enabled: daily ${String(hour).padStart(2, "0")}:00 local`);
|
|
33
|
-
emit("traework-checkin-enabled", { hour });
|
|
34
|
-
|
|
35
|
-
const stateMod = await import("../state.js");
|
|
36
|
-
const fetchImpl = deps.fetchImpl || compatFetch; // 注入缝:无此参数则测试只能打真上游(会真领积分),故必开
|
|
37
|
-
|
|
38
|
-
let running = false;
|
|
39
|
-
async function runOnce(reason) {
|
|
40
|
-
if (running) return { skipped: "already-running" };
|
|
41
|
-
running = true;
|
|
42
|
-
try {
|
|
43
|
-
const cfg = stateMod.loadProviderConfigs().traework || {};
|
|
44
|
-
const keys = [...(Array.isArray(cfg.keys) ? cfg.keys : [])];
|
|
45
|
-
const authList = [...(Array.isArray(cfg.auths) ? cfg.auths : [])];
|
|
46
|
-
if (!keys.length || !authList.length) {
|
|
47
|
-
emit("traework-checkin-no-accounts", { reason });
|
|
48
|
-
return { ok: false, reason: "no-accounts" };
|
|
49
|
-
}
|
|
50
|
-
const { checkinStatus, checkinClaimVerified } = await import("../providers/traework/checkin.js");
|
|
51
|
-
const results = await Promise.all(authList.map(async (auth, i) => {
|
|
52
|
-
const at = keys[i];
|
|
53
|
-
const uid = auth?.uid;
|
|
54
|
-
if (!uid || !at) return { uid, ok: false, msg: "missing key/uid" };
|
|
55
|
-
// keys 槽位不像凭据(被 CLI 误写过)时别去打上游,直接给人话
|
|
56
|
-
if (at.trim().length < 20 || [...at].some((c) => c.trim() === "")) {
|
|
57
|
-
return { uid, ok: false, msg: `keys[${i}] 不像 accessToken(${at.length} 字符),重跑 -provider traework login 回填` };
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const cred = { accessToken: at, deviceId: auth?.deviceId || "", uid };
|
|
61
|
-
const st = await checkinStatus({ cred, fetchImpl });
|
|
62
|
-
// 判据一律走复核:旧写法 claim 一跑完就 return ok:true,实测上游 checked_in 仍 false 而 state 记 ok:1
|
|
63
|
-
const r = await checkinClaimVerified({ cred, fetchImpl, before: st });
|
|
64
|
-
return { uid, ok: r.claimed || r.already, already: r.already, phantom: r.phantom, credits: r.credits, msg: r.msg };
|
|
65
|
-
} catch (e) {
|
|
66
|
-
return { uid, ok: false, msg: String(e?.message || e).slice(0, 120) };
|
|
67
|
-
}
|
|
68
|
-
}));
|
|
69
|
-
const okCount = results.filter((r) => r.ok).length;
|
|
70
|
-
const phantomCount = results.filter((r) => r.phantom).length;
|
|
71
|
-
// 只在真有人到账时才把 date 盖成今天:shouldCatchUp 的判据是 lastDate !== 今天,
|
|
72
|
-
// 谎报时盖 date 会把当日补签一并掐掉(本次那 100 分就是这么丢的)。
|
|
73
|
-
const prevState = stateMod.readState(stateMod.defaultStateFile()) || {};
|
|
74
|
-
const date = okCount > 0 ? todayKey() : String(prevState.traeworkCheckin?.date || "");
|
|
75
|
-
try {
|
|
76
|
-
stateMod.writeStateImmediate(stateMod.defaultStateFile(), {
|
|
77
|
-
traeworkCheckin: {
|
|
78
|
-
date,
|
|
79
|
-
at: Date.now(),
|
|
80
|
-
ok: okCount,
|
|
81
|
-
total: results.length,
|
|
82
|
-
phantom: phantomCount,
|
|
83
|
-
lastAttempt: todayKey(),
|
|
84
|
-
accounts: results.map((r) => ({ uid: String(r.uid || "").slice(0, 8), ok: r.ok, already: !!r.already, phantom: !!r.phantom, credits: Number(r.credits) || 0, msg: String(r.msg || "").slice(0, 120) })),
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
} catch {}
|
|
88
|
-
for (const r of results) {
|
|
89
|
-
emit("traework-checkin-account", { uid: String(r.uid || "").slice(0, 8), ok: r.ok, already: !!r.already, phantom: !!r.phantom, credits: Number(r.credits) || 0, msg: String(r.msg || "").slice(0, 120), reason });
|
|
90
|
-
}
|
|
91
|
-
if (phantomCount) emit("traework-checkin-phantom", { phantom: phantomCount, total: results.length, reason, hint: "上游回执未报错但复查 checked_in=false → 判未到账,不记为已签,下次启动仍补签" });
|
|
92
|
-
emit("traework-checkin-done", { date, ok: okCount, total: results.length, reason });
|
|
93
|
-
return { ok: okCount > 0, okCount, total: results.length };
|
|
94
|
-
} catch (e) {
|
|
95
|
-
emit("traework-checkin-failed", { error: String(e?.message || e).slice(0, 200), reason });
|
|
96
|
-
return { ok: false, error: String(e?.message || e).slice(0, 200) };
|
|
97
|
-
} finally {
|
|
98
|
-
running = false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 启动补签
|
|
103
|
-
try {
|
|
104
|
-
const lastDate = stateMod.readState(stateMod.defaultStateFile())?.traeworkCheckin?.date || "";
|
|
105
|
-
if (shouldCatchUp({ lastDate, hour })) {
|
|
106
|
-
setTimeout(() => { runOnce("catch-up").catch(() => {}); }, 60_000).unref?.();
|
|
107
|
-
console.log(`[traework-checkin] catch-up scheduled (last=${lastDate || "never"})`);
|
|
108
|
-
}
|
|
109
|
-
} catch {}
|
|
110
|
-
// 每日定时
|
|
111
|
-
function arm() {
|
|
112
|
-
const delay = nextRunDelayMs(new Date(), hour);
|
|
113
|
-
setTimeout(() => {
|
|
114
|
-
runOnce("daily").catch(() => {}).finally(arm);
|
|
115
|
-
}, delay).unref?.();
|
|
116
|
-
}
|
|
117
|
-
arm();
|
|
118
|
-
return { enabled: true, hour, runOnce };
|
|
1
|
+
/**
|
|
2
|
+
* traework 每日自动签到(daemon 内置),仿 src/runtime/workbuddy-checkin.js。
|
|
3
|
+
* 开关:MSLXDFF_TRAEWORK_CHECKIN=0 关闭(默认开);时间:MSLXDFF_TRAEWORK_CHECKIN_HOUR(默认 9 点)。
|
|
4
|
+
* 多账号全签 + 启动补签 + 每日定时。
|
|
5
|
+
*/
|
|
6
|
+
import { compatFetch } from "../compat.js";
|
|
7
|
+
import { todayKey, nextRunDelayMs, shouldCatchUp } from "./workbuddy-checkin.js";
|
|
8
|
+
|
|
9
|
+
export function isCheckinEnabled(env = process.env) {
|
|
10
|
+
const v = String(env.MSLXDFF_TRAEWORK_CHECKIN ?? "1").trim().toLowerCase();
|
|
11
|
+
return !["0", "false", "off", "no"].includes(v);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getCheckinHour(env = process.env) {
|
|
15
|
+
const h = Number(env.MSLXDFF_TRAEWORK_CHECKIN_HOUR);
|
|
16
|
+
return Number.isInteger(h) && h >= 0 && h <= 23 ? h : 9;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function setupTraeworkCheckin({ bus, logs, deps = {} } = {}) {
|
|
20
|
+
function emit(type, data = {}) {
|
|
21
|
+
const entry = { ts: Date.now(), type, ...data };
|
|
22
|
+
try { bus?.emit(entry); } catch {}
|
|
23
|
+
try { logs?.appendEvent?.(entry); } catch {}
|
|
24
|
+
try { console.log(`[traework-checkin] ${type} ${JSON.stringify(data)}`.slice(0, 500)); } catch {}
|
|
25
|
+
}
|
|
26
|
+
if (!isCheckinEnabled()) {
|
|
27
|
+
console.log("[traework-checkin] disabled (set MSLXDFF_TRAEWORK_CHECKIN=1 to enable)");
|
|
28
|
+
emit("traework-checkin-disabled");
|
|
29
|
+
return { enabled: false };
|
|
30
|
+
}
|
|
31
|
+
const hour = getCheckinHour();
|
|
32
|
+
console.log(`[traework-checkin] enabled: daily ${String(hour).padStart(2, "0")}:00 local`);
|
|
33
|
+
emit("traework-checkin-enabled", { hour });
|
|
34
|
+
|
|
35
|
+
const stateMod = await import("../state.js");
|
|
36
|
+
const fetchImpl = deps.fetchImpl || compatFetch; // 注入缝:无此参数则测试只能打真上游(会真领积分),故必开
|
|
37
|
+
|
|
38
|
+
let running = false;
|
|
39
|
+
async function runOnce(reason) {
|
|
40
|
+
if (running) return { skipped: "already-running" };
|
|
41
|
+
running = true;
|
|
42
|
+
try {
|
|
43
|
+
const cfg = stateMod.loadProviderConfigs().traework || {};
|
|
44
|
+
const keys = [...(Array.isArray(cfg.keys) ? cfg.keys : [])];
|
|
45
|
+
const authList = [...(Array.isArray(cfg.auths) ? cfg.auths : [])];
|
|
46
|
+
if (!keys.length || !authList.length) {
|
|
47
|
+
emit("traework-checkin-no-accounts", { reason });
|
|
48
|
+
return { ok: false, reason: "no-accounts" };
|
|
49
|
+
}
|
|
50
|
+
const { checkinStatus, checkinClaimVerified } = await import("../providers/traework/checkin.js");
|
|
51
|
+
const results = await Promise.all(authList.map(async (auth, i) => {
|
|
52
|
+
const at = keys[i];
|
|
53
|
+
const uid = auth?.uid;
|
|
54
|
+
if (!uid || !at) return { uid, ok: false, msg: "missing key/uid" };
|
|
55
|
+
// keys 槽位不像凭据(被 CLI 误写过)时别去打上游,直接给人话
|
|
56
|
+
if (at.trim().length < 20 || [...at].some((c) => c.trim() === "")) {
|
|
57
|
+
return { uid, ok: false, msg: `keys[${i}] 不像 accessToken(${at.length} 字符),重跑 -provider traework login 回填` };
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const cred = { accessToken: at, deviceId: auth?.deviceId || "", uid };
|
|
61
|
+
const st = await checkinStatus({ cred, fetchImpl });
|
|
62
|
+
// 判据一律走复核:旧写法 claim 一跑完就 return ok:true,实测上游 checked_in 仍 false 而 state 记 ok:1
|
|
63
|
+
const r = await checkinClaimVerified({ cred, fetchImpl, before: st });
|
|
64
|
+
return { uid, ok: r.claimed || r.already, already: r.already, phantom: r.phantom, credits: r.credits, msg: r.msg };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return { uid, ok: false, msg: String(e?.message || e).slice(0, 120) };
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
const okCount = results.filter((r) => r.ok).length;
|
|
70
|
+
const phantomCount = results.filter((r) => r.phantom).length;
|
|
71
|
+
// 只在真有人到账时才把 date 盖成今天:shouldCatchUp 的判据是 lastDate !== 今天,
|
|
72
|
+
// 谎报时盖 date 会把当日补签一并掐掉(本次那 100 分就是这么丢的)。
|
|
73
|
+
const prevState = stateMod.readState(stateMod.defaultStateFile()) || {};
|
|
74
|
+
const date = okCount > 0 ? todayKey() : String(prevState.traeworkCheckin?.date || "");
|
|
75
|
+
try {
|
|
76
|
+
stateMod.writeStateImmediate(stateMod.defaultStateFile(), {
|
|
77
|
+
traeworkCheckin: {
|
|
78
|
+
date,
|
|
79
|
+
at: Date.now(),
|
|
80
|
+
ok: okCount,
|
|
81
|
+
total: results.length,
|
|
82
|
+
phantom: phantomCount,
|
|
83
|
+
lastAttempt: todayKey(),
|
|
84
|
+
accounts: results.map((r) => ({ uid: String(r.uid || "").slice(0, 8), ok: r.ok, already: !!r.already, phantom: !!r.phantom, credits: Number(r.credits) || 0, msg: String(r.msg || "").slice(0, 120) })),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
} catch {}
|
|
88
|
+
for (const r of results) {
|
|
89
|
+
emit("traework-checkin-account", { uid: String(r.uid || "").slice(0, 8), ok: r.ok, already: !!r.already, phantom: !!r.phantom, credits: Number(r.credits) || 0, msg: String(r.msg || "").slice(0, 120), reason });
|
|
90
|
+
}
|
|
91
|
+
if (phantomCount) emit("traework-checkin-phantom", { phantom: phantomCount, total: results.length, reason, hint: "上游回执未报错但复查 checked_in=false → 判未到账,不记为已签,下次启动仍补签" });
|
|
92
|
+
emit("traework-checkin-done", { date, ok: okCount, total: results.length, reason });
|
|
93
|
+
return { ok: okCount > 0, okCount, total: results.length };
|
|
94
|
+
} catch (e) {
|
|
95
|
+
emit("traework-checkin-failed", { error: String(e?.message || e).slice(0, 200), reason });
|
|
96
|
+
return { ok: false, error: String(e?.message || e).slice(0, 200) };
|
|
97
|
+
} finally {
|
|
98
|
+
running = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 启动补签
|
|
103
|
+
try {
|
|
104
|
+
const lastDate = stateMod.readState(stateMod.defaultStateFile())?.traeworkCheckin?.date || "";
|
|
105
|
+
if (shouldCatchUp({ lastDate, hour })) {
|
|
106
|
+
setTimeout(() => { runOnce("catch-up").catch(() => {}); }, 60_000).unref?.();
|
|
107
|
+
console.log(`[traework-checkin] catch-up scheduled (last=${lastDate || "never"})`);
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
// 每日定时
|
|
111
|
+
function arm() {
|
|
112
|
+
const delay = nextRunDelayMs(new Date(), hour);
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
runOnce("daily").catch(() => {}).finally(arm);
|
|
115
|
+
}, delay).unref?.();
|
|
116
|
+
}
|
|
117
|
+
arm();
|
|
118
|
+
return { enabled: true, hour, runOnce };
|
|
119
119
|
}
|