creditdaddy 1.3.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.
@@ -259,19 +259,60 @@ async function fetchViaProxy(url, { method = 'GET', headers = {}, body = null, t
259
259
  let viaProxyImpl = null;
260
260
  export function _setViaProxyForTests(fn) { viaProxyImpl = fn || null; }
261
261
 
262
- export async function fetchJsonRace(url, { method = 'GET', headers = {}, body = null, timeoutMs = 15000, signal = null } = {}) {
262
+ /**
263
+ * 组合多个 AbortSignal 成一个:任一源触发即 abort。
264
+ * 与 AbortSignal.any 的区别——这里返回 { signal, releaseConnect },releaseConnect()
265
+ * 可摘掉 connectSignal 的转发监听:两段式超时里,响应头到达后连接段限时就该失效,
266
+ * 但整墙钟(wallSignal)和外部取消(external)要继续管住流式 body。
267
+ */
268
+ function combineSignals({ wallSignal, connectSignal, external }) {
269
+ const ctrl = new AbortController();
270
+ const onWall = () => ctrl.abort(wallSignal.reason);
271
+ const onConnect = () => ctrl.abort(connectSignal.reason);
272
+ const onExternal = () => ctrl.abort(external.reason);
273
+ if (wallSignal.aborted) ctrl.abort(wallSignal.reason);
274
+ else wallSignal.addEventListener('abort', onWall, { once: true });
275
+ if (connectSignal && !ctrl.signal.aborted) {
276
+ if (connectSignal.aborted) ctrl.abort(connectSignal.reason);
277
+ else connectSignal.addEventListener('abort', onConnect, { once: true });
278
+ }
279
+ if (external && !ctrl.signal.aborted) {
280
+ if (external.aborted) ctrl.abort(external.reason);
281
+ else external.addEventListener('abort', onExternal, { once: true });
282
+ }
283
+ // 连接段成功后调用:只摘掉 connectSignal 的转发,墙钟与外部取消继续管住 body 流
284
+ const releaseConnect = () => { connectSignal?.removeEventListener('abort', onConnect); };
285
+ return { signal: ctrl.signal, releaseConnect };
286
+ }
287
+
288
+ /**
289
+ * 直连 / 代理兜底的 fetch。可选 connectMs 启用两段式超时:
290
+ * - 拿到响应头前(DNS/TCP/TLS/首字节)限时 connectMs——上游黑洞挂死时几秒内
291
+ * 就失败换下一条路,而不是吊满整个 timeoutMs(网关补全场景尤其重要);
292
+ * - 拿到响应头后解除连接段限时,只保留 timeoutMs 整墙钟——流式补全可能合法地
293
+ * 跑几分钟,不能因为「连接快、生成慢」被误杀。
294
+ * 不传 connectMs 时退回单段 timeoutMs(旧行为,所有既有调用方不受影响)。
295
+ */
296
+ export async function fetchJsonRace(url, { method = 'GET', headers = {}, body = null, timeoutMs = 15000, connectMs = null, signal = null } = {}) {
263
297
  const useProxy = Boolean(effectiveProxy()) && !proxyBypass(url);
298
+ const twoPhase = Number.isFinite(connectMs) && connectMs > 0 && connectMs < timeoutMs;
264
299
  // 超时信号必须每次尝试各建一个:共用一个的话,代理优先模式下第一条路吃完 timeoutMs,
265
300
  // 兜底那条就对着已触发的 signal 发请求,必定瞬间失败——「代理优先」就此形同虚设
266
301
  const makeAttempt = () => {
267
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
268
- const abortSignal = signal ? AbortSignal.any([timeoutSignal, signal]) : timeoutSignal;
302
+ const wallSignal = AbortSignal.timeout(timeoutMs);
303
+ const connectSignal = twoPhase ? AbortSignal.timeout(connectMs) : null;
304
+ const { signal: abortSignal, releaseConnect } = combineSignals({ wallSignal, connectSignal, external: signal });
269
305
  const base = { method, headers: { ...headers }, ...(body != null ? { body: typeof body === 'string' ? body : JSON.stringify(body) } : {}), signal: abortSignal };
270
306
  return {
271
- direct: () => fetch(url, base),
272
- viaProxy: () => (viaProxyImpl
273
- ? viaProxyImpl(url, { method, headers, body, timeoutMs, signal: abortSignal })
274
- : fetchViaProxy(url, { method, headers, body, timeoutMs, signal: abortSignal })),
307
+ // 响应头到达 = 连接段成功:摘掉 connectSignal 转发,此后只有墙钟/外部取消能中断流
308
+ direct: async () => { const r = await fetch(url, base); releaseConnect(); return r; },
309
+ viaProxy: async () => {
310
+ const r = viaProxyImpl
311
+ ? await viaProxyImpl(url, { method, headers, body, timeoutMs, signal: abortSignal })
312
+ : await fetchViaProxy(url, { method, headers, body, timeoutMs, signal: abortSignal });
313
+ releaseConnect();
314
+ return r;
315
+ },
275
316
  };
276
317
  };
277
318
  const order = useProxy ? (proxyFirst() ? ['viaProxy', 'direct'] : ['direct', 'viaProxy']) : ['direct'];
@@ -322,7 +363,11 @@ async function bigmodelHeaders(token) {
322
363
  async function getJson(url, headers) {
323
364
  const res = await fetchJsonRace(url, { headers, timeoutMs: FETCH_TIMEOUT_MS });
324
365
  const text = await res.text();
325
- try { return JSON.parse(text); } catch { return { code: res.status, msg: text.slice(0, 120) }; }
366
+ try { return JSON.parse(text); } catch {
367
+ // 解析失败(含 HTTP 200 + 空 body,服务端偶发对无效 token 就是这样)绝不能伪装成 code:200,
368
+ // 否则 businessOk 会放行并让额度链提前返回“无套餐”,短路后面的 balance 尝试
369
+ return { code: res.ok ? -1 : res.status, msg: (text || `empty response (HTTP ${res.status})`).slice(0, 120) };
370
+ }
326
371
  }
327
372
 
328
373
  const businessOk = (v) => {
@@ -497,6 +542,12 @@ export function normalizePlans(previewResp) {
497
542
  return out;
498
543
  }
499
544
 
545
+ /** “当前用户不存在coding plan / 没有资格”属于无套餐的业务口径,不是查询失败(同 zcode-switch is_no_plan_message) */
546
+ function isNoPlanMessage(msg) {
547
+ const m = String(msg || '');
548
+ return m.includes('不存在coding plan') || m.includes('没有资格') || /no.*coding.*plan/i.test(m);
549
+ }
550
+
500
551
  /**
501
552
  * 额度查询:先试 BigModel Coding Plan(quota/limit),再试 Z.ai / Start Plan(billing/balance)。
502
553
  * 都没有有效套餐时返回 { empty: true }(不是错误:很多账号只用免费额度)。
@@ -505,28 +556,35 @@ export async function fetchZcodeQuota(account) {
505
556
  const tokens = candidateTokens(account);
506
557
  if (!tokens.length) throw new Error('账号快照里没有可用 token,请在 ZCode 登录后重新导入');
507
558
  let lastErr = null;
559
+ let sawNoPlan = false;
508
560
  let authFails = 0;
509
561
  for (const t of tokens) {
510
562
  try {
511
563
  const limit = await getJson(QUOTA_LIMIT_URL, await bigmodelHeaders(t));
512
564
  if (businessOk(limit)) {
513
565
  const sub = await getJson(SUBSCRIPTION_URL, await bigmodelHeaders(t)).catch(() => null);
514
- return { ...normalizeQuotaLimit(limit, sub), source: 'bigmodel' };
566
+ const ov = normalizeQuotaLimit(limit, sub);
567
+ // BigModel 侧“有效响应但零额度项”不代表账号没额度(Start Plan 账号走 balance 有额度),
568
+ // 不能提前返回 empty,必须继续把 balance 链路试完——与 zcode-switch 每 token 双端点连查一致
569
+ if (!ov.empty) return { ...ov, source: 'bigmodel' };
515
570
  }
516
571
  if (limit?.code === 401) authFails++;
572
+ else if (isNoPlanMessage(limit?.msg)) sawNoPlan = true;
517
573
  else lastErr = limit?.msg || lastErr;
518
574
  } catch (e) { lastErr = e.message; }
519
575
  }
520
576
  for (const t of billingTokens(account)) {
521
577
  try {
522
578
  const bal = await getJson(`${BILLING_BALANCE_URL}?app_version=${await zcodeAppVersion()}`, await zaiHeaders(t, ensureVirtualDeviceMid(account)));
579
+ // balance 是账号级总余额端点,有效答复(含“确无套餐”)即可采信;
580
+ // 与 bigmodel 侧不同——那里“有效但零额度项”不代表账号没额度,必须继续往下试
523
581
  if (businessOk(bal)) return { ...normalizeBalance(bal), source: 'zcode.z.ai' };
524
582
  if (bal?.code === 401) authFails++;
525
583
  } catch (e) { lastErr = e.message; }
526
584
  }
527
585
  if (authFails && authFails >= tokens.length) throw new Error('鉴权失败,登录可能已过期,请在 ZCode 重新登录后重新导入');
528
- // “当前用户不存在coding plan”之类属于无套餐,按空额度返回
529
- if (!lastErr || /不存在|no.*plan/i.test(lastErr)) {
586
+ // 无套餐口径:显式的“不存在coding plan”或整条链没有任何错误
587
+ if (!lastErr || sawNoPlan) {
530
588
  return { total: 0, used: 0, remaining: 0, unit: '', plan: null, parts: [], empty: true, exceeded: false };
531
589
  }
532
590
  throw new Error(`额度查询失败:${lastErr}`);
@@ -22,7 +22,8 @@ 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 {}
@@ -38,13 +39,17 @@ export function getZcodeCompletionProvider() { return completionProvider; }
38
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
 
@@ -379,7 +384,9 @@ export async function handleGateway(req, res) {
379
384
  res.writeHead(405, { 'Content-Type': 'application/json' });
380
385
  return res.end(JSON.stringify({ error: 'POST /gateway/zcode/v1/messages(Anthropic /v1/messages 形态)' }));
381
386
  }
382
- if (!(await gatewayEnabled())) {
387
+ // settings 一次读齐:开关 + 局域网鉴权共用,省掉一次 settings.json 磁盘读(热路径每请求都走)
388
+ const settings = await loadSettings();
389
+ if (settings.zcodeGateway !== true) {
383
390
  res.writeHead(503, { 'Content-Type': 'application/json' });
384
391
  return res.end(JSON.stringify({ error: 'ZCode 免费额度网关未开启(面板 → ZCode → 网关开关)' }));
385
392
  }
@@ -389,7 +396,6 @@ export async function handleGateway(req, res) {
389
396
  // 鉴权:本机回环免密;开局域网后仅白名单内 IP 免密直连(如 10Router 所在机器——zcode-free 本就是免授权供应商)
390
397
  const remote = (req.socket && req.socket.remoteAddress) || '';
391
398
  if (!isLoopbackRemote(remote)) {
392
- const settings = await loadSettings();
393
399
  if (settings.zcodeGatewayLan !== true) {
394
400
  res.writeHead(403, { 'Content-Type': 'application/json' });
395
401
  return res.end(JSON.stringify({ error: '网关未允许局域网访问(面板 → ZCode → 接口设置)' }));
@@ -418,6 +424,11 @@ export async function handleGateway(req, res) {
418
424
  stats.calls += 1;
419
425
 
420
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(); });
421
432
  // 首发不带验证码(客户端真实流量多数直过);上游 3007/403 时求解并在此后的
422
433
  // 尝试上挂新 token(zcode-api 同策略)。provider 为 null(NAS/CLI)时首过路径依然可用。
423
434
  let captcha = null;
@@ -434,8 +445,6 @@ export async function handleGateway(req, res) {
434
445
  }
435
446
 
436
447
  let upstream;
437
- const controller = new AbortController();
438
- req.on('close', () => controller.abort());
439
448
  try {
440
449
  // 请求面完全镜像 ZCode 客户端真实流量(R1 捕获):全套 identity 头 + 客户端
441
450
  // 系统提示词/提醒块/metadata 形状。验证码不预挂——服务端未风控时直过;
@@ -449,10 +458,11 @@ export async function handleGateway(req, res) {
449
458
  headers,
450
459
  body: built.body,
451
460
  timeoutMs: UPSTREAM_TIMEOUT_MS,
452
- signal: controller.signal,
461
+ connectMs: UPSTREAM_CONNECT_MS,
462
+ signal: clientAbort.signal,
453
463
  });
454
464
  } catch (e) {
455
- if (controller.signal.aborted) return; // 客户端先断了
465
+ if (clientAbort.signal.aborted) return; // 客户端先断了
456
466
  attempted.push({ account: label, ok: false, error: e.message });
457
467
  continue;
458
468
  }
@@ -477,7 +487,7 @@ export async function handleGateway(req, res) {
477
487
  if (isAuthError(200, text)) {
478
488
  markDead(account);
479
489
  attempted.push({ account: label, ok: false, error: 'JWT 已失效,账号拉黑' });
480
- logger.info('ZCODE-GW', `${label} JWT 失效(200 业务码)`);
490
+ logger.warn('ZCODE-GW', `${label} JWT 失效(200 业务码)`);
481
491
  continue;
482
492
  }
483
493
  if (isExhausted(200, text)) {
@@ -532,7 +542,8 @@ export async function handleGateway(req, res) {
532
542
  }
533
543
  }
534
544
  } catch (e) {
535
- logger.warn('ZCODE-GW', `流式转发中断:${e.message}`);
545
+ // 客户端主动断开(点停止/换一句)就会在这里结束——属正常收尾,不是故障,降为 debug
546
+ logger.debug('ZCODE-GW', `流式转发中断:${e.message}`);
536
547
  }
537
548
  res.end();
538
549
  return true;
@@ -543,9 +554,9 @@ export async function handleGateway(req, res) {
543
554
  if (isCaptchaError(upstream.status, text)) {
544
555
  invalidateCaptcha();
545
556
  attempted.push({ account: label, ok: false, error: `验证码被拒(${upstream.status})`, captcha: true });
546
- logger.info('ZCODE-GW', `${label} 验证码被拒 (${formatUpstreamError(upstream.status, text)})`);
557
+ logger.warn('ZCODE-GW', `${label} 验证码被拒 (${formatUpstreamError(upstream.status, text)})`);
547
558
  try { captcha = await ensureCaptcha(true); } catch (e) {
548
- logger.info('ZCODE-GW', `验证码重解失败:${e.message}`);
559
+ logger.warn('ZCODE-GW', `验证码重解失败:${e.message}`);
549
560
  captcha = null;
550
561
  }
551
562
  continue;
@@ -576,7 +587,8 @@ export async function handleGateway(req, res) {
576
587
  return true;
577
588
  }
578
589
 
579
- logger.warn('ZCODE-GW', `全部 ${attempted.length} 次尝试失败`);
590
+ const failedDetail = summarizeAttempts(attempted);
591
+ logger.warn('ZCODE-GW', `全部 ${attempted.length} 次尝试失败${failedDetail ? ':' + failedDetail : ''}(下一请求自动重试)`);
580
592
  res.writeHead(502, { 'Content-Type': 'application/json' });
581
593
  res.end(JSON.stringify({ error: 'ZCode 网关:所有账号尝试均失败', attempts: attempted }));
582
594
  return true;
@@ -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
  }