mslxdff 0.1.200 → 0.1.203
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/package.json +38 -38
- package/plugins/README.md +14 -14
- package/plugins/prefer-model.mjs.example +14 -14
- package/src/anthropic/count-tokens.js +61 -61
- package/src/anthropic/request-body.js +126 -126
- package/src/anthropic/shared.js +18 -18
- package/src/anthropic/sse-aggregate.js +56 -56
- package/src/anthropic/translate.js +287 -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/serial-trial.js +283 -283
- package/src/chat-pipeline/terminal-verdict.js +147 -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 +149 -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 +62 -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 -251
- package/src/plugins.js +107 -107
- 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 -165
- package/src/providers/raccoon/credits.js +185 -175
- package/src/providers/raccoon/crypto.js +43 -0
- 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 -87
- 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/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 +83 -83
- package/src/routes/index.js +147 -147
- package/src/routes/messages-route.js +365 -365
- package/src/routes/peers.js +23 -1
- 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/bootstrap.js +8 -0
- 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 -0
- 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/server.js +7 -0
- 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/record.js +106 -106
- package/src/usage/report.js +195 -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
- package/docs/cli_help_mini.md +0 -146
- package/src/providers/AGENTS.md +0 -49
- package/src/routes/AGENTS.md +0 -37
package/src/warp-pool/revive.js
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
// warp-pool · 复活与取证(真机事故后新增,2026-10-16)。
|
|
2
|
-
//
|
|
3
|
-
// 三件事,都来自组长 VPS 上暴露的缺陷:
|
|
4
|
-
//
|
|
5
|
-
// 1. 隧道退出后必须自愈:原先只有「全池不通」才补拉,一条死隧道会让池静默从 4 降到 3,直到 daemon 重启。
|
|
6
|
-
// 2. 入池后必须实测各节点真实出口:WARP 的 IPv4 出口是共享 NAT,「N 设备」很可能折叠成一个出口
|
|
7
|
-
// (2026-10-17 组长 VPS 实测:4 条隧道只拿到 2 个出口地址 ⇒ 隧道数 ≠ 上游名额数)。
|
|
8
|
-
// 3. 复活必须**真的活下来**(2026-10-16 组长 VPS 事故):`tunnels.has(port)` 只证明「spawn 被调用了」,
|
|
9
|
-
// wireproxy 起一条死一条(端口被上一代孤儿进程占着 ⇒ bind 失败 ⇒ 退出码 1),旧逻辑次次判「已复活」
|
|
10
|
-
// ⇒ 下一次退出立刻再复活 ⇒ 无限重启刷屏,该端口永远起不来。
|
|
11
|
-
//
|
|
12
|
-
// 复活只走**磁盘上已有的 conf**:进程挂了 ≠ 设备失效,重注册只会白烧 CF 配额。
|
|
13
|
-
import { probeTunnel } from "./wireproxy.js";
|
|
14
|
-
import { fmtExits } from "./report.js";
|
|
15
|
-
|
|
16
|
-
/** 出口取证目标:Cloudflare 自己的 trace 端点,回 `ip=` 一行(不烧上游 token)。 */
|
|
17
|
-
export const EXIT_PROBE_URL = "https://cloudflare.com/cdn-cgi/trace";
|
|
18
|
-
|
|
19
|
-
/** 从 cdn-cgi/trace 正文里取出口地址;取不到返回 ""。 */
|
|
20
|
-
export function parseExitIp(text) {
|
|
21
|
-
const m = String(text || "").match(/^\s*ip=(\S+)\s*$/m);
|
|
22
|
-
return m ? m[1] : "";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** 子进程末行输出当退出原因(wireproxy 的真实报错都在 stderr);没有输出返回 ""。 */
|
|
26
|
-
export function tailReason(tail, max = 300) {
|
|
27
|
-
const lines = String(tail || "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
28
|
-
return lines.length ? lines[lines.length - 1].slice(0, max) : "";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 按端口独立的退避复活器。
|
|
33
|
-
* @param {{ revive: (port:number)=>Promise<boolean>, isAlive?: (port:number)=>boolean, minGapMs?: number,
|
|
34
|
-
* maxGapMs?: number, settleMs?: number, now?: ()=>number, sleep?: Function,
|
|
35
|
-
* schedule?: Function, cancel?: Function, log?: (s:string)=>void }} opts
|
|
36
|
-
* 复活是否算数由 `isAlive` 在 `settleMs` 后复查;失败按指数退避并**自己用定时器安排下一次重试**。
|
|
37
|
-
*/
|
|
38
|
-
export function createReviver({ revive, isAlive, minGapMs = 30_000, maxGapMs = 10 * 60_000, settleMs = 1_200, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), schedule = (fn, ms) => setTimeout(fn, ms), cancel = (h) => clearTimeout(h), log = () => {} } = {}) {
|
|
39
|
-
/** @type {Map<number, {attempts:number, nextAt:number, busy:boolean}>} */
|
|
40
|
-
const state = new Map();
|
|
41
|
-
|
|
42
|
-
function entry(port) {
|
|
43
|
-
let s = state.get(port);
|
|
44
|
-
if (!s) {
|
|
45
|
-
s = { attempts: 0, nextAt: 0, busy: false };
|
|
46
|
-
state.set(port, s);
|
|
47
|
-
}
|
|
48
|
-
return s;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** @type {Map<number, object>} 每端口最多一个待触发的重试定时器 */
|
|
52
|
-
const timers = new Map();
|
|
53
|
-
|
|
54
|
-
/** 安排「到点自动重试」;没有它,日志里那句「N s 后自动再试」就是空话。 */
|
|
55
|
-
function armRetry(port, ms) {
|
|
56
|
-
if (timers.has(port)) return;
|
|
57
|
-
const h = schedule(() => {
|
|
58
|
-
timers.delete(port);
|
|
59
|
-
void attempt(port);
|
|
60
|
-
}, Math.max(0, ms));
|
|
61
|
-
if (h && typeof h.unref === "function") h.unref();
|
|
62
|
-
timers.set(port, h);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 复活一次:首次立即复活,失败按 30s→10min 指数退避并自己安排下一次重试。
|
|
67
|
-
* 复活算不算数由 `isAlive` 在 `settleMs` 后复查——不许拿「spawn 被调用了」冒充「活下来了」。
|
|
68
|
-
*/
|
|
69
|
-
async function attempt(port) {
|
|
70
|
-
const s = entry(port);
|
|
71
|
-
if (s.busy) return false;
|
|
72
|
-
const wait = Math.max(0, s.nextAt - now());
|
|
73
|
-
if (wait > 0) {
|
|
74
|
-
log(`[warp-pool] :${port} 距上次复活不足退避间隔,${Math.ceil(wait / 1000)}s 后再试(防抖动刷屏,不是故障)`);
|
|
75
|
-
armRetry(port, wait);
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
s.busy = true;
|
|
79
|
-
s.attempts += 1;
|
|
80
|
-
const gap = Math.min(minGapMs * 2 ** (s.attempts - 1), maxGapMs);
|
|
81
|
-
let ok = false;
|
|
82
|
-
try {
|
|
83
|
-
ok = await revive(port);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
log(`[warp-pool] :${port} 复活抛出异常:${String(e?.message || e)}`);
|
|
86
|
-
}
|
|
87
|
-
if (ok && isAlive) {
|
|
88
|
-
if (settleMs > 0) await sleep(settleMs);
|
|
89
|
-
ok = isAlive(port);
|
|
90
|
-
if (!ok) log(`[warp-pool] :${port} 复活后进程立刻又退出 —— 不认这次复活(原因见上面那条「意外退出」)`);
|
|
91
|
-
}
|
|
92
|
-
s.busy = false;
|
|
93
|
-
if (ok) {
|
|
94
|
-
const n = s.attempts;
|
|
95
|
-
s.attempts = 0;
|
|
96
|
-
s.nextAt = now() + minGapMs; // 稳定期:期间再退出先等一轮,防「起一条死一条」把日志刷爆
|
|
97
|
-
log(`[warp-pool] :${port} 已复活(第 ${n} 次尝试成功);${Math.round(minGapMs / 1000)}s 内若又退出会先等一轮再重启`);
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
s.nextAt = now() + gap;
|
|
101
|
-
const hint = s.attempts >= 3 ? ` 排查:ss -ltnp | grep :${port};或前台跑隧道二进制 -c node-${port}.conf 看真实报错` : "";
|
|
102
|
-
log(`[warp-pool] :${port} 复活未成功(第 ${s.attempts} 次),${Math.round(gap / 1000)}s 后自动再试。${hint}`);
|
|
103
|
-
armRetry(port, gap);
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
onExit: (port) => attempt(port),
|
|
109
|
-
/** 探活成功时调用:清掉退避账与待触发定时器,恢复正常节奏。 */
|
|
110
|
-
noteHealthy(port) {
|
|
111
|
-
const h = timers.get(port);
|
|
112
|
-
if (h) {
|
|
113
|
-
try { cancel(h); } catch {}
|
|
114
|
-
timers.delete(port);
|
|
115
|
-
}
|
|
116
|
-
state.delete(port);
|
|
117
|
-
},
|
|
118
|
-
stats() {
|
|
119
|
-
return [...state.entries()].map(([port, s]) => ({
|
|
120
|
-
port,
|
|
121
|
-
attempts: s.attempts,
|
|
122
|
-
nextAt: s.nextAt,
|
|
123
|
-
cooldownMsLeft: Math.max(0, s.nextAt - now()),
|
|
124
|
-
}));
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
/** 池关闭时调用:别再有定时器去打复活。 */
|
|
128
|
-
stop() {
|
|
129
|
-
for (const h of timers.values()) {
|
|
130
|
-
try { cancel(h); } catch {}
|
|
131
|
-
}
|
|
132
|
-
timers.clear();
|
|
133
|
-
state.clear();
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 入池后实测各节点真实出口地址并打日志(含撞车警告)。
|
|
140
|
-
* 只读探测:打 Cloudflare 的 trace 端点,不碰上游配额。
|
|
141
|
-
* @param {{ ports?: number[], dir?: string, fetchImpl?: Function, timeoutMs?: number, log?: Function }} [opts]
|
|
142
|
-
* @returns {Promise<{ ip: string, nodes: number[] }[]>} 按出口地址分组的节点清单
|
|
143
|
-
*/
|
|
144
|
-
export async function labelExits({ ports = [], dir = "", fetchImpl, timeoutMs = 12_000, log = () => {} } = {}) {
|
|
145
|
-
const byIp = new Map();
|
|
146
|
-
let failed = 0;
|
|
147
|
-
for (const port of ports) {
|
|
148
|
-
let ip = "";
|
|
149
|
-
try {
|
|
150
|
-
const res = await probeTunnel({ port, baseUrl: EXIT_PROBE_URL, path: "", fetchImpl, timeoutMs, wantText: true });
|
|
151
|
-
ip = typeof res?.text === "string" ? parseExitIp(res.text) : "";
|
|
152
|
-
} catch {
|
|
153
|
-
ip = "";
|
|
154
|
-
}
|
|
155
|
-
if (!ip) {
|
|
156
|
-
failed += 1;
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (!byIp.has(ip)) byIp.set(ip, []);
|
|
160
|
-
byIp.get(ip).push(port);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const groups = [...byIp.entries()].map(([ip, nodes]) => ({ ip, nodes }));
|
|
164
|
-
const line = fmtExits({ groups, dir });
|
|
165
|
-
if (line) log(line);
|
|
166
|
-
if (failed) log(`[warp-pool] ${failed} 个节点出口取证失败(不影响进池,只影响轮换时「出口到底变没变」的比对)`);
|
|
167
|
-
return groups;
|
|
168
|
-
}
|
|
1
|
+
// warp-pool · 复活与取证(真机事故后新增,2026-10-16)。
|
|
2
|
+
//
|
|
3
|
+
// 三件事,都来自组长 VPS 上暴露的缺陷:
|
|
4
|
+
//
|
|
5
|
+
// 1. 隧道退出后必须自愈:原先只有「全池不通」才补拉,一条死隧道会让池静默从 4 降到 3,直到 daemon 重启。
|
|
6
|
+
// 2. 入池后必须实测各节点真实出口:WARP 的 IPv4 出口是共享 NAT,「N 设备」很可能折叠成一个出口
|
|
7
|
+
// (2026-10-17 组长 VPS 实测:4 条隧道只拿到 2 个出口地址 ⇒ 隧道数 ≠ 上游名额数)。
|
|
8
|
+
// 3. 复活必须**真的活下来**(2026-10-16 组长 VPS 事故):`tunnels.has(port)` 只证明「spawn 被调用了」,
|
|
9
|
+
// wireproxy 起一条死一条(端口被上一代孤儿进程占着 ⇒ bind 失败 ⇒ 退出码 1),旧逻辑次次判「已复活」
|
|
10
|
+
// ⇒ 下一次退出立刻再复活 ⇒ 无限重启刷屏,该端口永远起不来。
|
|
11
|
+
//
|
|
12
|
+
// 复活只走**磁盘上已有的 conf**:进程挂了 ≠ 设备失效,重注册只会白烧 CF 配额。
|
|
13
|
+
import { probeTunnel } from "./wireproxy.js";
|
|
14
|
+
import { fmtExits } from "./report.js";
|
|
15
|
+
|
|
16
|
+
/** 出口取证目标:Cloudflare 自己的 trace 端点,回 `ip=` 一行(不烧上游 token)。 */
|
|
17
|
+
export const EXIT_PROBE_URL = "https://cloudflare.com/cdn-cgi/trace";
|
|
18
|
+
|
|
19
|
+
/** 从 cdn-cgi/trace 正文里取出口地址;取不到返回 ""。 */
|
|
20
|
+
export function parseExitIp(text) {
|
|
21
|
+
const m = String(text || "").match(/^\s*ip=(\S+)\s*$/m);
|
|
22
|
+
return m ? m[1] : "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 子进程末行输出当退出原因(wireproxy 的真实报错都在 stderr);没有输出返回 ""。 */
|
|
26
|
+
export function tailReason(tail, max = 300) {
|
|
27
|
+
const lines = String(tail || "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
28
|
+
return lines.length ? lines[lines.length - 1].slice(0, max) : "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 按端口独立的退避复活器。
|
|
33
|
+
* @param {{ revive: (port:number)=>Promise<boolean>, isAlive?: (port:number)=>boolean, minGapMs?: number,
|
|
34
|
+
* maxGapMs?: number, settleMs?: number, now?: ()=>number, sleep?: Function,
|
|
35
|
+
* schedule?: Function, cancel?: Function, log?: (s:string)=>void }} opts
|
|
36
|
+
* 复活是否算数由 `isAlive` 在 `settleMs` 后复查;失败按指数退避并**自己用定时器安排下一次重试**。
|
|
37
|
+
*/
|
|
38
|
+
export function createReviver({ revive, isAlive, minGapMs = 30_000, maxGapMs = 10 * 60_000, settleMs = 1_200, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), schedule = (fn, ms) => setTimeout(fn, ms), cancel = (h) => clearTimeout(h), log = () => {} } = {}) {
|
|
39
|
+
/** @type {Map<number, {attempts:number, nextAt:number, busy:boolean}>} */
|
|
40
|
+
const state = new Map();
|
|
41
|
+
|
|
42
|
+
function entry(port) {
|
|
43
|
+
let s = state.get(port);
|
|
44
|
+
if (!s) {
|
|
45
|
+
s = { attempts: 0, nextAt: 0, busy: false };
|
|
46
|
+
state.set(port, s);
|
|
47
|
+
}
|
|
48
|
+
return s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @type {Map<number, object>} 每端口最多一个待触发的重试定时器 */
|
|
52
|
+
const timers = new Map();
|
|
53
|
+
|
|
54
|
+
/** 安排「到点自动重试」;没有它,日志里那句「N s 后自动再试」就是空话。 */
|
|
55
|
+
function armRetry(port, ms) {
|
|
56
|
+
if (timers.has(port)) return;
|
|
57
|
+
const h = schedule(() => {
|
|
58
|
+
timers.delete(port);
|
|
59
|
+
void attempt(port);
|
|
60
|
+
}, Math.max(0, ms));
|
|
61
|
+
if (h && typeof h.unref === "function") h.unref();
|
|
62
|
+
timers.set(port, h);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 复活一次:首次立即复活,失败按 30s→10min 指数退避并自己安排下一次重试。
|
|
67
|
+
* 复活算不算数由 `isAlive` 在 `settleMs` 后复查——不许拿「spawn 被调用了」冒充「活下来了」。
|
|
68
|
+
*/
|
|
69
|
+
async function attempt(port) {
|
|
70
|
+
const s = entry(port);
|
|
71
|
+
if (s.busy) return false;
|
|
72
|
+
const wait = Math.max(0, s.nextAt - now());
|
|
73
|
+
if (wait > 0) {
|
|
74
|
+
log(`[warp-pool] :${port} 距上次复活不足退避间隔,${Math.ceil(wait / 1000)}s 后再试(防抖动刷屏,不是故障)`);
|
|
75
|
+
armRetry(port, wait);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
s.busy = true;
|
|
79
|
+
s.attempts += 1;
|
|
80
|
+
const gap = Math.min(minGapMs * 2 ** (s.attempts - 1), maxGapMs);
|
|
81
|
+
let ok = false;
|
|
82
|
+
try {
|
|
83
|
+
ok = await revive(port);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
log(`[warp-pool] :${port} 复活抛出异常:${String(e?.message || e)}`);
|
|
86
|
+
}
|
|
87
|
+
if (ok && isAlive) {
|
|
88
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
89
|
+
ok = isAlive(port);
|
|
90
|
+
if (!ok) log(`[warp-pool] :${port} 复活后进程立刻又退出 —— 不认这次复活(原因见上面那条「意外退出」)`);
|
|
91
|
+
}
|
|
92
|
+
s.busy = false;
|
|
93
|
+
if (ok) {
|
|
94
|
+
const n = s.attempts;
|
|
95
|
+
s.attempts = 0;
|
|
96
|
+
s.nextAt = now() + minGapMs; // 稳定期:期间再退出先等一轮,防「起一条死一条」把日志刷爆
|
|
97
|
+
log(`[warp-pool] :${port} 已复活(第 ${n} 次尝试成功);${Math.round(minGapMs / 1000)}s 内若又退出会先等一轮再重启`);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
s.nextAt = now() + gap;
|
|
101
|
+
const hint = s.attempts >= 3 ? ` 排查:ss -ltnp | grep :${port};或前台跑隧道二进制 -c node-${port}.conf 看真实报错` : "";
|
|
102
|
+
log(`[warp-pool] :${port} 复活未成功(第 ${s.attempts} 次),${Math.round(gap / 1000)}s 后自动再试。${hint}`);
|
|
103
|
+
armRetry(port, gap);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
onExit: (port) => attempt(port),
|
|
109
|
+
/** 探活成功时调用:清掉退避账与待触发定时器,恢复正常节奏。 */
|
|
110
|
+
noteHealthy(port) {
|
|
111
|
+
const h = timers.get(port);
|
|
112
|
+
if (h) {
|
|
113
|
+
try { cancel(h); } catch {}
|
|
114
|
+
timers.delete(port);
|
|
115
|
+
}
|
|
116
|
+
state.delete(port);
|
|
117
|
+
},
|
|
118
|
+
stats() {
|
|
119
|
+
return [...state.entries()].map(([port, s]) => ({
|
|
120
|
+
port,
|
|
121
|
+
attempts: s.attempts,
|
|
122
|
+
nextAt: s.nextAt,
|
|
123
|
+
cooldownMsLeft: Math.max(0, s.nextAt - now()),
|
|
124
|
+
}));
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/** 池关闭时调用:别再有定时器去打复活。 */
|
|
128
|
+
stop() {
|
|
129
|
+
for (const h of timers.values()) {
|
|
130
|
+
try { cancel(h); } catch {}
|
|
131
|
+
}
|
|
132
|
+
timers.clear();
|
|
133
|
+
state.clear();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 入池后实测各节点真实出口地址并打日志(含撞车警告)。
|
|
140
|
+
* 只读探测:打 Cloudflare 的 trace 端点,不碰上游配额。
|
|
141
|
+
* @param {{ ports?: number[], dir?: string, fetchImpl?: Function, timeoutMs?: number, log?: Function }} [opts]
|
|
142
|
+
* @returns {Promise<{ ip: string, nodes: number[] }[]>} 按出口地址分组的节点清单
|
|
143
|
+
*/
|
|
144
|
+
export async function labelExits({ ports = [], dir = "", fetchImpl, timeoutMs = 12_000, log = () => {} } = {}) {
|
|
145
|
+
const byIp = new Map();
|
|
146
|
+
let failed = 0;
|
|
147
|
+
for (const port of ports) {
|
|
148
|
+
let ip = "";
|
|
149
|
+
try {
|
|
150
|
+
const res = await probeTunnel({ port, baseUrl: EXIT_PROBE_URL, path: "", fetchImpl, timeoutMs, wantText: true });
|
|
151
|
+
ip = typeof res?.text === "string" ? parseExitIp(res.text) : "";
|
|
152
|
+
} catch {
|
|
153
|
+
ip = "";
|
|
154
|
+
}
|
|
155
|
+
if (!ip) {
|
|
156
|
+
failed += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!byIp.has(ip)) byIp.set(ip, []);
|
|
160
|
+
byIp.get(ip).push(port);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const groups = [...byIp.entries()].map(([ip, nodes]) => ({ ip, nodes }));
|
|
164
|
+
const line = fmtExits({ groups, dir });
|
|
165
|
+
if (line) log(line);
|
|
166
|
+
if (failed) log(`[warp-pool] ${failed} 个节点出口取证失败(不影响进池,只影响轮换时「出口到底变没变」的比对)`);
|
|
167
|
+
return groups;
|
|
168
|
+
}
|