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.
@@ -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, zaiHeaders, fetchCaptchaConfig, fetchJsonRace, fetchZcodeQuota } from './zcodeClient.js';
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, saveState } from './store.js';
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
- await saveSettings({ zcodeGateway: v === true });
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 Set(); // JWT 失效的账号
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(id) { dead.add(id); cooling.set(id, Number.MAX_SAFE_INTEGER); }
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 (dead.has(a.id)) continue;
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 (dead.has(a.id)) n.dead++;
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
- const st = await loadState();
149
- const saved = st?.zcodeGatewayExhausted || {};
150
- saved[accountId] = entry;
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
- const st = await loadState();
164
- const saved = st?.zcodeGatewayExhausted;
165
- if (saved && saved[accountId]) {
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) { reject(Object.assign(new Error('request body too large'), { status: 413 })); req.destroy(); return; }
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)|3007|3012|captcha|unusual activity/i.test(text);
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|令牌已过期|验证不正确/.test(text);
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)|1113|1005|余额不足|无可用资源包|exceed quota|insufficient/i.test(text);
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
- if (!(await gatewayEnabled())) {
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 (dead.has(account.id) || isQuotaExhausted(account.id) || (cooling.get(account.id) || 0) > Date.now()) continue;
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.id);
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
- signal: controller.signal,
461
+ connectMs: UPSTREAM_CONNECT_MS,
462
+ signal: clientAbort.signal,
409
463
  });
410
464
  } catch (e) {
411
- if (controller.signal.aborted) return; // 客户端先断了
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
- if (v && typeof v.code === 'number') {
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.id);
488
+ markDead(account);
433
489
  attempted.push({ account: label, ok: false, error: 'JWT 已失效,账号拉黑' });
434
- logger.info('ZCODE-GW', `${label} JWT 失效(200 业务码)`);
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
- logger.warn('ZCODE-GW', `流式转发中断:${e.message}`);
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.info('ZCODE-GW', `${label} 验证码被拒 (${formatUpstreamError(upstream.status, text)})`);
557
+ logger.warn('ZCODE-GW', `${label} 验证码被拒 (${formatUpstreamError(upstream.status, text)})`);
501
558
  try { captcha = await ensureCaptcha(true); } catch (e) {
502
- logger.info('ZCODE-GW', `验证码重解失败:${e.message}`);
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.id);
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
- logger.warn('ZCODE-GW', `全部 ${attempted.length} 次尝试失败`);
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: [...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
  }
@@ -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\\YangYu\\zproxy-test\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.",
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 2026-09-29.\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>",
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\":\"ses_f0a670f7b1f1\"}"
27
+ "user_id": "{\"account_uuid\":\"\",\"session_id\":\"ses_\"}"
28
28
  }
29
29
  }