creditdaddy 1.2.0 → 1.3.2
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/LICENSE +21 -21
- package/README.md +40 -10
- package/bin/creditdaddy.js +131 -2
- package/package.json +29 -29
- package/src/accounts.js +18 -7
- package/src/authDevice.js +3 -0
- package/src/bgdaemon.js +332 -0
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +10 -4
- package/src/constants.js +4 -2
- package/src/daemon.js +137 -13
- package/src/localDetect.js +147 -147
- package/src/logger.js +104 -6
- package/src/minimaxAuth.js +127 -0
- package/src/minimaxClient.js +339 -0
- package/src/minimaxGateway.js +410 -0
- package/src/minimaxLocal.js +259 -0
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +459 -36
- package/src/providers.js +26 -0
- package/src/qoderApp.js +3 -9
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +65 -7
- package/src/tenrouter.js +8 -0
- package/src/tenrouterAccounts.js +38 -3
- package/src/traeClient.js +6 -1
- package/src/traeGateway.js +679 -0
- package/src/transfer.js +24 -1
- package/src/usageSync.js +505 -505
- package/src/winProc.js +36 -0
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +93 -18
- package/src/zcodeGateway.js +104 -46
- package/src/zcodePlanShape.json +3 -3
- package/src/zcrypto.js +160 -160
package/src/zcodeGateway.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ZCode 免费额度网关 — Start Plan 体验包(GLM-5.3-Flash)的本地补全代理。
|
|
3
3
|
*
|
|
4
|
-
* 数据面:POST /gateway/v1/messages(Anthropic /v1/messages 形态,10router
|
|
4
|
+
* 数据面:POST /gateway/zcode/v1/messages(Anthropic /v1/messages 形态,10router
|
|
5
5
|
* 建一个 anthropic-compatible 自定义节点指向这里即可)。
|
|
6
6
|
*
|
|
7
7
|
* 链路:账号轮换(store 里 provider=zcode、可解析出 zcodejwttoken 的账号)
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
* daemon 加绑定/密钥,见 README)。NAS/纯 CLI 环境没有验证码提供者,网关开不了。
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { claimToken,
|
|
19
|
+
import { claimToken, fetchCaptchaConfig, fetchJsonRace, fetchZcodeQuota } from './zcodeClient.js';
|
|
20
20
|
import { readFileSync } from 'node:fs';
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
|
|
24
24
|
// ZCode 客户端 plan 请求形状(从本机客户端真实流量捕获的载荷模板:系统提示词数组 +
|
|
25
|
-
// currentDate 提醒块 + metadata 会话形状)。网关请求镜像此形状——这就是 405 风控的通过票。
|
|
25
|
+
// currentDate 提醒块 + metadata 会话形状)。网关请求镜像此形状——这就是 405/3012 风控的通过票。
|
|
26
|
+
// 提示词已脱敏(去掉抓包来源的用户路径/会话 id),避免把第三方隐私随请求外发(见 issue #16)。
|
|
26
27
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
let PLAN_SHAPE = null;
|
|
28
29
|
try { PLAN_SHAPE = JSON.parse(readFileSync(path.join(__dirname, 'zcodePlanShape.json'), 'utf8')); } catch {}
|
|
@@ -35,16 +36,20 @@ import { gatewayHostSuggestion } from './tenrouter.js';
|
|
|
35
36
|
let completionProvider = null;
|
|
36
37
|
export function setZcodeCompletionProvider(fn) { completionProvider = typeof fn === 'function' ? fn : null; }
|
|
37
38
|
export function getZcodeCompletionProvider() { return completionProvider; }
|
|
38
|
-
import { loadAccounts, loadSettings, saveSettings, loadState,
|
|
39
|
+
import { loadAccounts, loadSettings, saveSettings, loadState, withState } from './store.js';
|
|
39
40
|
import crypto from 'node:crypto';
|
|
40
41
|
import os from 'node:os';
|
|
41
|
-
import { logger } from './logger.js';
|
|
42
|
+
import { logger, summarizeAttempts } from './logger.js';
|
|
42
43
|
|
|
43
44
|
const PLAN_MESSAGES_URL = 'https://zcode.z.ai/api/v1/zcode-plan/anthropic/v1/messages';
|
|
44
45
|
const CAPTCHA_CACHE_TTL_MS = 30_000;
|
|
45
46
|
const ACCOUNT_COOLING_MS = 5 * 60_000;
|
|
46
47
|
const MAX_ATTEMPTS = 6;
|
|
48
|
+
// 上游补全墙钟上限(响应头到齐后 body 仍可流式跑这么久)
|
|
47
49
|
const UPSTREAM_TIMEOUT_MS = 600_000;
|
|
50
|
+
// 连接段(DNS/TCP/TLS/首字节)限时:上游黑洞挂死时几秒内换下一条路 / 代理兜底,
|
|
51
|
+
// 而不是吊满 10 分钟——这是「网关很慢 / 卡住」体感的主要来源
|
|
52
|
+
const UPSTREAM_CONNECT_MS = 15_000;
|
|
48
53
|
|
|
49
54
|
// ── 开关(持久化在 settings.zcodeGateway) ──
|
|
50
55
|
|
|
@@ -53,14 +58,21 @@ export async function gatewayEnabled() {
|
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
export async function setGatewayEnabled(v) {
|
|
56
|
-
|
|
61
|
+
const on = v === true;
|
|
62
|
+
await saveSettings({ zcodeGateway: on });
|
|
63
|
+
// 开启网关视为「重新开始」:清掉进程内的拉黑/冷却状态,
|
|
64
|
+
// 否则重新登录导入后旧黑名单仍会把账号挡在轮换队列外(与 MiniMax 网关同款 bug)。
|
|
65
|
+
if (on) {
|
|
66
|
+
dead.clear();
|
|
67
|
+
cooling.clear();
|
|
68
|
+
}
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
// ── 账号轮换状态(进程内;重启即清零) ──
|
|
60
72
|
|
|
61
73
|
let rrIndex = 0;
|
|
62
74
|
const cooling = new Map(); // accountId → 冷却截止(ms)
|
|
63
|
-
const dead = new
|
|
75
|
+
const dead = new Map(); // accountId → 拉黑时的凭据指纹(指纹变化 = 已重新登录导入,自动复活)
|
|
64
76
|
|
|
65
77
|
function markCooling(id, ms = ACCOUNT_COOLING_MS) { cooling.set(id, Date.now() + ms); }
|
|
66
78
|
|
|
@@ -76,7 +88,29 @@ export function __resetForTests() {
|
|
|
76
88
|
stats.calls = 0;
|
|
77
89
|
stats.lastAccount = null;
|
|
78
90
|
}
|
|
79
|
-
function markDead(
|
|
91
|
+
function markDead(account) {
|
|
92
|
+
dead.set(account.id, credFingerprint(account));
|
|
93
|
+
cooling.set(account.id, Number.MAX_SAFE_INTEGER);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 拉黑时的凭据指纹:重新登录/重新导入会整体替换 meta.credentials,指纹变化即可复活 */
|
|
97
|
+
function credFingerprint(a) {
|
|
98
|
+
return `${a.meta?.credentials?.zcodejwttoken || ''}|${a.token || ''}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 账号是否仍处于拉黑状态。拉黑记的是「当时那份凭据」的指纹:
|
|
103
|
+
* 重新登录导入换了 JWT 后指纹不匹配 → 自动复活(清黑名单与冷却)。
|
|
104
|
+
*/
|
|
105
|
+
function isDead(account) {
|
|
106
|
+
const fp = dead.get(account.id);
|
|
107
|
+
if (fp === undefined) return false;
|
|
108
|
+
if (fp === credFingerprint(account)) return true;
|
|
109
|
+
dead.delete(account.id);
|
|
110
|
+
cooling.delete(account.id);
|
|
111
|
+
logger.info('ZCODE-GW', `${account.name || account.uid || account.id} 凭据已更新,解除拉黑`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
80
114
|
|
|
81
115
|
/** 可参与轮换的账号(有可解析 plan JWT、未冷却/未拉黑),按轮转序排列 */
|
|
82
116
|
async function rotationQueue() {
|
|
@@ -85,7 +119,7 @@ async function rotationQueue() {
|
|
|
85
119
|
const now = Date.now();
|
|
86
120
|
const ready = [];
|
|
87
121
|
for (const a of all) {
|
|
88
|
-
if (
|
|
122
|
+
if (isDead(a)) continue;
|
|
89
123
|
if (isQuotaExhausted(a.id)) continue; // 0 额度打标,不再轮换
|
|
90
124
|
if ((cooling.get(a.id) || 0) > now) continue;
|
|
91
125
|
try { claimToken(a); ready.push(a); } catch { /* 快照里没有 plan JWT,跳过 */ }
|
|
@@ -102,7 +136,7 @@ async function unavailableReason() {
|
|
|
102
136
|
const now = Date.now();
|
|
103
137
|
const n = { dead: 0, exhausted: 0, cooling: 0, noJwt: 0 };
|
|
104
138
|
for (const a of all) {
|
|
105
|
-
if (
|
|
139
|
+
if (isDead(a)) n.dead++;
|
|
106
140
|
else if (isQuotaExhausted(a.id)) n.exhausted++;
|
|
107
141
|
else if ((cooling.get(a.id) || 0) > now) n.cooling++;
|
|
108
142
|
else n.noJwt++; // 未拉黑/未打标/未冷却却不在队列 → 快照里没有可解析的 plan JWT
|
|
@@ -145,10 +179,9 @@ async function hydrateMarks() {
|
|
|
145
179
|
|
|
146
180
|
async function persistMark(accountId, entry) {
|
|
147
181
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await saveState({ ...st, zcodeGatewayExhausted: saved });
|
|
182
|
+
await withState((st) => {
|
|
183
|
+
st.zcodeGatewayExhausted = { ...(st.zcodeGatewayExhausted || {}), [accountId]: entry };
|
|
184
|
+
});
|
|
152
185
|
} catch { /* 持久化失败不影响内存态 */ }
|
|
153
186
|
}
|
|
154
187
|
|
|
@@ -160,12 +193,9 @@ export async function clearQuotaMark(accountId) {
|
|
|
160
193
|
quotaCache.delete(accountId);
|
|
161
194
|
if (!dead.has(accountId)) cooling.delete(accountId);
|
|
162
195
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
delete saved[accountId];
|
|
167
|
-
await saveState({ ...st, zcodeGatewayExhausted: saved });
|
|
168
|
-
}
|
|
196
|
+
await withState((st) => {
|
|
197
|
+
if (st?.zcodeGatewayExhausted?.[accountId]) delete st.zcodeGatewayExhausted[accountId];
|
|
198
|
+
});
|
|
169
199
|
} catch { /* 持久化失败不影响内存态 */ }
|
|
170
200
|
}
|
|
171
201
|
|
|
@@ -234,7 +264,14 @@ function readRawBody(req, limitBytes = 20 * 1024 * 1024) {
|
|
|
234
264
|
let size = 0;
|
|
235
265
|
req.on('data', (c) => {
|
|
236
266
|
size += c.length;
|
|
237
|
-
if (size > limitBytes) {
|
|
267
|
+
if (size > limitBytes) {
|
|
268
|
+
// 只 pause 不 destroy:destroy 会把 socket 打死,调用方随后写的 413 就发不出去,
|
|
269
|
+
// 客户端只看到 ECONNRESET,分不清「自己请求体写太大」和「网络断了」。
|
|
270
|
+
// 响应写完(res 'finish')再由调用方收尾断开。
|
|
271
|
+
req.pause();
|
|
272
|
+
reject(Object.assign(new Error(`request body too large (limit ${Math.floor(limitBytes / 1024 / 1024)}MB)`), { status: 413 }));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
238
275
|
chunks.push(c);
|
|
239
276
|
});
|
|
240
277
|
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
@@ -242,16 +279,26 @@ function readRawBody(req, limitBytes = 20 * 1024 * 1024) {
|
|
|
242
279
|
});
|
|
243
280
|
}
|
|
244
281
|
|
|
245
|
-
// 业务码可能包在 HTTP 200 里(上游对部分错误回 200 + {code}
|
|
282
|
+
// 业务码可能包在 HTTP 200 里(上游对部分错误回 200 + {code}),两类都要看。
|
|
283
|
+
// 裸数字必须加 \b 词边界:否则 "code":4010 会被当成 401 认证失败(→ 账号被永久拉黑),
|
|
284
|
+
// 耗时 10050ms / request-id=11137 / 30071 也会被当成额度耗尽或验证码(→ 冷却 30 分钟 / 空跑一次验证码)。
|
|
246
285
|
const isCaptchaError = (status, text) =>
|
|
247
|
-
status === 405 || /"code"\s*:\s*(3007|3012)
|
|
286
|
+
status === 405 || /"code"\s*:\s*(3007|3012)\b|\b(3007|3012)\b|captcha|unusual activity/i.test(text);
|
|
248
287
|
|
|
249
288
|
const isAuthError = (status, text) =>
|
|
250
|
-
status === 401 || /"code"\s*:\s*401
|
|
289
|
+
status === 401 || /"code"\s*:\s*401\b|\b401\b|令牌已过期|验证不正确/.test(text);
|
|
251
290
|
|
|
252
291
|
const isExhausted = (status, text) =>
|
|
253
|
-
status === 402 || /"code"\s*:\s*(1005|1113)
|
|
254
|
-
|
|
292
|
+
status === 402 || /"code"\s*:\s*(1005|1113)\b|\b(1113|1005)\b|余额不足|无可用资源包|exceed quota|insufficient/i.test(text);
|
|
293
|
+
|
|
294
|
+
// 上游成功也会带 code(0 / 200),别把它们当业务错误——与 zcodeClient.businessOk 同一口径
|
|
295
|
+
const BUSINESS_OK = new Set([0, 200]);
|
|
296
|
+
/** 从上游 JSON 信封里取业务码;字符串数字("3007")也认,取不到返回 null */
|
|
297
|
+
function businessCode(v) {
|
|
298
|
+
if (!v || typeof v !== 'object' || v.code === undefined || v.code === null) return null;
|
|
299
|
+
const n = Number(v.code);
|
|
300
|
+
return Number.isFinite(n) ? n : null;
|
|
301
|
+
}
|
|
255
302
|
// 组装与 ZCode 客户端真实流量一致的 plan 请求(镜像 R1 捕获,2026-09-29)
|
|
256
303
|
function buildPlanRequest(rawBody, { token, userId }) {
|
|
257
304
|
const ver = '3.14.3';
|
|
@@ -335,9 +382,11 @@ export function remoteAllowed(remote, allowList) {
|
|
|
335
382
|
export async function handleGateway(req, res) {
|
|
336
383
|
if (req.method !== 'POST') {
|
|
337
384
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
338
|
-
return res.end(JSON.stringify({ error: 'POST /gateway/v1/messages(Anthropic /v1/messages 形态)' }));
|
|
385
|
+
return res.end(JSON.stringify({ error: 'POST /gateway/zcode/v1/messages(Anthropic /v1/messages 形态)' }));
|
|
339
386
|
}
|
|
340
|
-
|
|
387
|
+
// settings 一次读齐:开关 + 局域网鉴权共用,省掉一次 settings.json 磁盘读(热路径每请求都走)
|
|
388
|
+
const settings = await loadSettings();
|
|
389
|
+
if (settings.zcodeGateway !== true) {
|
|
341
390
|
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
342
391
|
return res.end(JSON.stringify({ error: 'ZCode 免费额度网关未开启(面板 → ZCode → 网关开关)' }));
|
|
343
392
|
}
|
|
@@ -347,7 +396,6 @@ export async function handleGateway(req, res) {
|
|
|
347
396
|
// 鉴权:本机回环免密;开局域网后仅白名单内 IP 免密直连(如 10Router 所在机器——zcode-free 本就是免授权供应商)
|
|
348
397
|
const remote = (req.socket && req.socket.remoteAddress) || '';
|
|
349
398
|
if (!isLoopbackRemote(remote)) {
|
|
350
|
-
const settings = await loadSettings();
|
|
351
399
|
if (settings.zcodeGatewayLan !== true) {
|
|
352
400
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
353
401
|
return res.end(JSON.stringify({ error: '网关未允许局域网访问(面板 → ZCode → 接口设置)' }));
|
|
@@ -361,6 +409,8 @@ export async function handleGateway(req, res) {
|
|
|
361
409
|
let rawBody;
|
|
362
410
|
try { rawBody = await readRawBody(req); } catch (e) {
|
|
363
411
|
res.writeHead(e.status || 400, { 'Content-Type': 'application/json' });
|
|
412
|
+
// 超限时请求体还没读完:先把 413 发出去,发完再断开,避免客户端继续往这灌数据
|
|
413
|
+
res.on('finish', () => req.destroy());
|
|
364
414
|
return res.end(JSON.stringify({ error: e.message }));
|
|
365
415
|
}
|
|
366
416
|
|
|
@@ -374,24 +424,27 @@ export async function handleGateway(req, res) {
|
|
|
374
424
|
stats.calls += 1;
|
|
375
425
|
|
|
376
426
|
const attempted = [];
|
|
427
|
+
// 客户端断开 → 取消当前上游请求。整个重试循环共用一个 controller/listener:
|
|
428
|
+
// 放在循环里每轮 new + req.on('close') 会在同一 req 上累积最多 MAX_ATTEMPTS 个监听器
|
|
429
|
+
// (MaxListenersExceededWarning + 泄漏)。
|
|
430
|
+
const clientAbort = new AbortController();
|
|
431
|
+
req.once('close', () => { if (!res.writableEnded) clientAbort.abort(); });
|
|
377
432
|
// 首发不带验证码(客户端真实流量多数直过);上游 3007/403 时求解并在此后的
|
|
378
433
|
// 尝试上挂新 token(zcode-api 同策略)。provider 为 null(NAS/CLI)时首过路径依然可用。
|
|
379
434
|
let captcha = null;
|
|
380
435
|
for (let i = 0; i < Math.min(MAX_ATTEMPTS, queue.length * 2); i++) {
|
|
381
436
|
const account = queue[i % queue.length];
|
|
382
437
|
// 本轮已被打标/冷却/拉黑的号不再重打(第二圈只为验证码重试准备)
|
|
383
|
-
if (
|
|
438
|
+
if (isDead(account) || isQuotaExhausted(account.id) || (cooling.get(account.id) || 0) > Date.now()) continue;
|
|
384
439
|
const label = account.name || account.uid || account.id;
|
|
385
440
|
let token;
|
|
386
441
|
try { token = claimToken(account); } catch (e) {
|
|
387
442
|
attempted.push({ account: label, ok: false, error: e.message });
|
|
388
|
-
markDead(account
|
|
443
|
+
markDead(account);
|
|
389
444
|
continue;
|
|
390
445
|
}
|
|
391
446
|
|
|
392
447
|
let upstream;
|
|
393
|
-
const controller = new AbortController();
|
|
394
|
-
req.on('close', () => controller.abort());
|
|
395
448
|
try {
|
|
396
449
|
// 请求面完全镜像 ZCode 客户端真实流量(R1 捕获):全套 identity 头 + 客户端
|
|
397
450
|
// 系统提示词/提醒块/metadata 形状。验证码不预挂——服务端未风控时直过;
|
|
@@ -405,10 +458,11 @@ export async function handleGateway(req, res) {
|
|
|
405
458
|
headers,
|
|
406
459
|
body: built.body,
|
|
407
460
|
timeoutMs: UPSTREAM_TIMEOUT_MS,
|
|
408
|
-
|
|
461
|
+
connectMs: UPSTREAM_CONNECT_MS,
|
|
462
|
+
signal: clientAbort.signal,
|
|
409
463
|
});
|
|
410
464
|
} catch (e) {
|
|
411
|
-
if (
|
|
465
|
+
if (clientAbort.signal.aborted) return; // 客户端先断了
|
|
412
466
|
attempted.push({ account: label, ok: false, error: e.message });
|
|
413
467
|
continue;
|
|
414
468
|
}
|
|
@@ -421,7 +475,9 @@ export async function handleGateway(req, res) {
|
|
|
421
475
|
const text = await upstream.text().catch(() => '');
|
|
422
476
|
let v;
|
|
423
477
|
try { v = JSON.parse(text); } catch { v = null; }
|
|
424
|
-
|
|
478
|
+
const code = businessCode(v);
|
|
479
|
+
// 上游成功信封也可能带 code(0 / 200),那不是业务错误;字符串数字("3007")同样要拦
|
|
480
|
+
if (code !== null && !BUSINESS_OK.has(code)) {
|
|
425
481
|
if (isCaptchaError(200, text)) {
|
|
426
482
|
invalidateCaptcha();
|
|
427
483
|
attempted.push({ account: label, ok: false, error: '验证码被拒(200 业务码)', captcha: true });
|
|
@@ -429,9 +485,9 @@ export async function handleGateway(req, res) {
|
|
|
429
485
|
continue;
|
|
430
486
|
}
|
|
431
487
|
if (isAuthError(200, text)) {
|
|
432
|
-
markDead(account
|
|
488
|
+
markDead(account);
|
|
433
489
|
attempted.push({ account: label, ok: false, error: 'JWT 已失效,账号拉黑' });
|
|
434
|
-
logger.
|
|
490
|
+
logger.warn('ZCODE-GW', `${label} JWT 失效(200 业务码)`);
|
|
435
491
|
continue;
|
|
436
492
|
}
|
|
437
493
|
if (isExhausted(200, text)) {
|
|
@@ -486,7 +542,8 @@ export async function handleGateway(req, res) {
|
|
|
486
542
|
}
|
|
487
543
|
}
|
|
488
544
|
} catch (e) {
|
|
489
|
-
|
|
545
|
+
// 客户端主动断开(点停止/换一句)就会在这里结束——属正常收尾,不是故障,降为 debug
|
|
546
|
+
logger.debug('ZCODE-GW', `流式转发中断:${e.message}`);
|
|
490
547
|
}
|
|
491
548
|
res.end();
|
|
492
549
|
return true;
|
|
@@ -497,15 +554,15 @@ export async function handleGateway(req, res) {
|
|
|
497
554
|
if (isCaptchaError(upstream.status, text)) {
|
|
498
555
|
invalidateCaptcha();
|
|
499
556
|
attempted.push({ account: label, ok: false, error: `验证码被拒(${upstream.status})`, captcha: true });
|
|
500
|
-
logger.
|
|
557
|
+
logger.warn('ZCODE-GW', `${label} 验证码被拒 (${formatUpstreamError(upstream.status, text)})`);
|
|
501
558
|
try { captcha = await ensureCaptcha(true); } catch (e) {
|
|
502
|
-
logger.
|
|
559
|
+
logger.warn('ZCODE-GW', `验证码重解失败:${e.message}`);
|
|
503
560
|
captcha = null;
|
|
504
561
|
}
|
|
505
562
|
continue;
|
|
506
563
|
}
|
|
507
564
|
if (isAuthError(upstream.status, text)) {
|
|
508
|
-
markDead(account
|
|
565
|
+
markDead(account);
|
|
509
566
|
attempted.push({ account: label, ok: false, error: 'JWT 已失效,账号拉黑(重新登录后再导入)' });
|
|
510
567
|
logger.debug('ZCODE-GW', `${label} JWT 失效,拉黑`);
|
|
511
568
|
continue;
|
|
@@ -530,7 +587,8 @@ export async function handleGateway(req, res) {
|
|
|
530
587
|
return true;
|
|
531
588
|
}
|
|
532
589
|
|
|
533
|
-
|
|
590
|
+
const failedDetail = summarizeAttempts(attempted);
|
|
591
|
+
logger.warn('ZCODE-GW', `全部 ${attempted.length} 次尝试失败${failedDetail ? ':' + failedDetail : ''}(下一请求自动重试)`);
|
|
534
592
|
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
535
593
|
res.end(JSON.stringify({ error: 'ZCode 网关:所有账号尝试均失败', attempts: attempted }));
|
|
536
594
|
return true;
|
|
@@ -562,9 +620,9 @@ export async function gatewayStatus() {
|
|
|
562
620
|
.map((a) => ({ id: a.id, name: a.name || a.uid || a.id, remaining: quotaCache.get(a.id)?.remaining ?? 0 })),
|
|
563
621
|
accounts: all.length,
|
|
564
622
|
accountsWithJwt: withJwt,
|
|
565
|
-
cooling: all.filter((a) => (cooling.get(a.id) || 0) > now).map((a) => a.name || a.uid || a.id),
|
|
566
|
-
dead:
|
|
567
|
-
endpoint: '/gateway/v1/messages',
|
|
623
|
+
cooling: all.filter((a) => !isDead(a) && (cooling.get(a.id) || 0) > now).map((a) => a.name || a.uid || a.id),
|
|
624
|
+
dead: all.filter((a) => isDead(a)).map((a) => a.name || a.uid || a.id),
|
|
625
|
+
endpoint: '/gateway/zcode/v1/messages',
|
|
568
626
|
note: hasCaptcha ? null : '需要桌面版 CreditDaddy(隐藏窗口验证码),纯 CLI / NAS 环境不可用',
|
|
569
627
|
};
|
|
570
628
|
}
|
package/src/zcodePlanShape.json
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"type": "text",
|
|
19
|
-
"text": "\n\n# Communicating with the user\n\nYour text output is what the user reads; they usually can't see your thinking or the raw tool results. Write it for a teammate who stepped away and is catching up, not for a log file: they don't know the codenames or shorthand you created along the way, and they didn't watch your process unfold. Before your first tool call, say in a sentence what you're about to do; while working, give brief updates when you find something load-bearing or change direction.\n\nText you write between tool calls may not be shown to the user. Everything the user needs from this turn — answers, summaries, findings, conclusions, deliverables — must be in the final text message of your turn, with no tool calls after it. Keep text between tool calls to brief status notes. If something important appeared only mid-turn or in your thinking, restate it in that final message.\n\nLead with the outcome. Your first sentence after finishing should answer \"what happened\" or \"what did you find\" — the thing the user would ask for if they said \"just give me the TLDR.\" Supporting detail and reasoning come after, for readers who want them.\n\nBeing readable and being concise are different things, and readable matters more. If the user has to reread your summary or ask you to explain, any time saved by brevity is gone. The way to keep output short is to be selective about what you include (drop details that don't change what the reader would do next), not to compress the writing into fragments, abbreviations, arrow chains like `A → B → fails`, or jargon. What you do include, write in complete sentences with the technical terms spelled out. Don't make the reader cross-reference labels or numbering you invented earlier; say what you mean in place.\n\nMatch the response to the question: a simple question gets a direct answer in prose, not headers and sections. Use tables only for short enumerable facts, with explanations in the surrounding prose rather than the cells. Calibrate to the user — a bit tighter for an expert, more explanatory for someone newer.\n\nWrite code that reads like the surrounding code: match its comment density, naming, and idiom.\nOnly write a code comment to state a constraint the code itself can't show — never to say where it came from, what the next line does, or why your change is correct; that's you talking to the reviewer, not the next reader, and it's noise the moment the PR merges.\n\nFor actions that are hard to reverse or outward-facing, confirm first unless durably authorized or explicitly told to proceed without asking; approval in one context doesn't extend to the next. Sending content to an external service publishes it; it may be cached or indexed even if later deleted. Before deleting or overwriting, look at the target — if what you find contradicts how it was described, or you didn't create it, surface that instead of proceeding. Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.\n\n# Environment\nYou have been invoked in the following environment:\n- Primary working directory: C:\\Users
|
|
19
|
+
"text": "\n\n# Communicating with the user\n\nYour text output is what the user reads; they usually can't see your thinking or the raw tool results. Write it for a teammate who stepped away and is catching up, not for a log file: they don't know the codenames or shorthand you created along the way, and they didn't watch your process unfold. Before your first tool call, say in a sentence what you're about to do; while working, give brief updates when you find something load-bearing or change direction.\n\nText you write between tool calls may not be shown to the user. Everything the user needs from this turn — answers, summaries, findings, conclusions, deliverables — must be in the final text message of your turn, with no tool calls after it. Keep text between tool calls to brief status notes. If something important appeared only mid-turn or in your thinking, restate it in that final message.\n\nLead with the outcome. Your first sentence after finishing should answer \"what happened\" or \"what did you find\" — the thing the user would ask for if they said \"just give me the TLDR.\" Supporting detail and reasoning come after, for readers who want them.\n\nBeing readable and being concise are different things, and readable matters more. If the user has to reread your summary or ask you to explain, any time saved by brevity is gone. The way to keep output short is to be selective about what you include (drop details that don't change what the reader would do next), not to compress the writing into fragments, abbreviations, arrow chains like `A → B → fails`, or jargon. What you do include, write in complete sentences with the technical terms spelled out. Don't make the reader cross-reference labels or numbering you invented earlier; say what you mean in place.\n\nMatch the response to the question: a simple question gets a direct answer in prose, not headers and sections. Use tables only for short enumerable facts, with explanations in the surrounding prose rather than the cells. Calibrate to the user — a bit tighter for an expert, more explanatory for someone newer.\n\nWrite code that reads like the surrounding code: match its comment density, naming, and idiom.\nOnly write a code comment to state a constraint the code itself can't show — never to say where it came from, what the next line does, or why your change is correct; that's you talking to the reviewer, not the next reader, and it's noise the moment the PR merges.\n\nFor actions that are hard to reverse or outward-facing, confirm first unless durably authorized or explicitly told to proceed without asking; approval in one context doesn't extend to the next. Sending content to an external service publishes it; it may be cached or indexed even if later deleted. Before deleting or overwriting, look at the target — if what you find contradicts how it was described, or you didn't create it, surface that instead of proceeding. Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.\n\n# Environment\nYou have been invoked in the following environment:\n- Primary working directory: C:\\Users\\<user>\\<workspace>\n- Is a git repository: no\n- Platform: win32\n- Shell: bash.exe\n- OS Version: win32 10.0.19045 x64\n- You are powered by the model named bigmodel-api/glm-5.3-flash.\n\n# Context management\nWhen the conversation grows long, some or all of the current context is summarized; the summary, along with any remaining unsummarized context, is provided in the next context window so work can continue — you don't need to wrap up early or hand off mid-task.\n\nWhen you have enough information to act, act. Do not re-derive facts already established in the conversation, re-litigate a decision the user has already made, or narrate options you will not pursue. If you are weighing a choice, give a recommendation, not an exhaustive survey\n\nYou are operating autonomously. The user is not watching in real time and cannot answer questions mid-task, so asking 'Want me to…?' or 'Shall I…?' will block the work. For reversible actions that follow from the original request, proceed without asking. Stop only for destructive actions or genuine scope changes the user must decide. Offering follow-ups after the task is done is fine; asking permission before doing the work is not.\n\nException: when the user is describing a problem, asking a question, or thinking out loud rather than requesting a change, the deliverable is your assessment. Report your findings and stop. Don't apply a fix until they ask for one.\n\nBefore ending your turn, check your last paragraph. If it is a plan, an analysis, a question, a list of next steps, or a promise about work you have not done ('I'll…', 'let me know when…'), do that work now with tool calls. That includes retrying after errors and gathering missing information yourself. Do not stop because the context or session is long. End your turn only when the task is complete or you are blocked on input only the user can provide.\n\nBefore running a command that changes system state — restarts, deletes, config edits — check that the evidence actually supports that specific action. A signal that pattern-matches to a known failure may have a different cause.",
|
|
20
20
|
"cache_control": {
|
|
21
21
|
"type": "ephemeral"
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
],
|
|
25
|
-
"reminderText": "<system-reminder>As you answer the user's questions, you can use the following context:\n# currentDate\nToday's date is
|
|
25
|
+
"reminderText": "<system-reminder>As you answer the user's questions, you can use the following context:\n# currentDate\nToday's date is YYYY-MM-DD.\n\n IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.</system-reminder>",
|
|
26
26
|
"metadata": {
|
|
27
|
-
"user_id": "{\"account_uuid\":\"\",\"session_id\":\"
|
|
27
|
+
"user_id": "{\"account_uuid\":\"\",\"session_id\":\"ses_\"}"
|
|
28
28
|
}
|
|
29
29
|
}
|