@misaka-net/misakanet-setup 0.8.0 → 0.8.1

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.
@@ -197,7 +197,7 @@ function readText(path) {
197
197
  * except the two that name the version (measured 2026-10-22: the 0.6.0 release pull request
198
198
  * failed 9 e2e legs on exactly that).
199
199
  */
200
- const VERSION = '0.8.0' // x-release-please-version
200
+ const VERSION = '0.8.1' // x-release-please-version
201
201
 
202
202
  /**
203
203
  * The shape of a client id, in one place.
@@ -1245,6 +1245,40 @@ let lastResponseOk = false;
1245
1245
  // `--verify` can print is what actually came back (`{}` is not a diagnosis).
1246
1246
  let lastResponsePreview = '';
1247
1247
 
1248
+ // The server's retry horizon on the last response, in seconds, when it sent one: the `Retry-After`
1249
+ // response header (seconds or an HTTP-date), the standard shape `docs/index.html` already reads on
1250
+ // 429s. Per-request, so a horizon from an earlier call can never leak into a later failure's words.
1251
+ let lastRetryAfterSeconds = null;
1252
+
1253
+ /** `Retry-After` → seconds. Both forms the header allows; anything else is "no horizon", not 0. */
1254
+ function retryAfterSeconds(headerValue) {
1255
+ const raw = String(headerValue || '').trim();
1256
+ if (!raw) return null;
1257
+ if (/^\d+$/.test(raw)) return Number(raw);
1258
+ const at = Date.parse(raw);
1259
+ if (Number.isNaN(at)) return null;
1260
+ return Math.max(0, Math.round((at - Date.now()) / 1000));
1261
+ }
1262
+
1263
+ /**
1264
+ * How long the server says to wait before re-running, when it says. The failure body's `retry_after`
1265
+ * (seconds, HTTP `Retry-After` semantics) wins over the header, and neither is guessed: a server that
1266
+ * stays silent gets no horizon sentence at all.
1267
+ */
1268
+ function retryHorizonOf(result) {
1269
+ const fromBody = result && (result.retry_after ?? result.retry_after_seconds);
1270
+ const n = Number(fromBody);
1271
+ if (Number.isFinite(n) && n > 0) return Math.round(n);
1272
+ return lastRetryAfterSeconds;
1273
+ }
1274
+
1275
+ /** "45 秒" / "15 分钟" / "2 小时" — the horizon in the words a person reads a clock in. */
1276
+ function humanWait(seconds) {
1277
+ if (seconds < 60) return `${seconds} 秒`;
1278
+ if (seconds < 3600) return `${Math.round(seconds / 60)} 分钟`;
1279
+ return `${Math.round(seconds / 3600)} 小时`;
1280
+ }
1281
+
1248
1282
  async function mcpRequest(method, params, timeoutMs = 6000, urlOverride = '') {
1249
1283
  const headers = {
1250
1284
  'Content-Type': 'application/json',
@@ -1260,6 +1294,7 @@ async function mcpRequest(method, params, timeoutMs = 6000, urlOverride = '') {
1260
1294
  lastRequestKind = '';
1261
1295
  lastResponseOk = false;
1262
1296
  lastResponsePreview = '';
1297
+ lastRetryAfterSeconds = null;
1263
1298
  try {
1264
1299
  const response = await fetch(urlOverride || ENDPOINT, {
1265
1300
  method: 'POST',
@@ -1271,6 +1306,9 @@ async function mcpRequest(method, params, timeoutMs = 6000, urlOverride = '') {
1271
1306
  // below, not by this line — but "it answered with something that is not a handshake" and
1272
1307
  // "nothing answered" are different facts (see probeOutcome()).
1273
1308
  lastResponseOk = true;
1309
+ lastRetryAfterSeconds = retryAfterSeconds(
1310
+ response.headers && typeof response.headers.get === 'function'
1311
+ ? response.headers.get('Retry-After') : null);
1274
1312
  const payload = await response.json();
1275
1313
  lastResponsePreview = JSON.stringify(payload).slice(0, 160);
1276
1314
  if (payload && payload.error) {
@@ -1618,8 +1656,15 @@ async function ensureIdentity() {
1618
1656
  : serverCode
1619
1657
  ? `服务端拒绝了这次注册(${serverCode})`
1620
1658
  : `多半是网络(${lastRequestError || '没有收到响应'})`;
1659
+ // …and when the server says *how long* to wait, say that too instead of "稍后". The horizon is
1660
+ // never guessed and never rounded into a promise: a server that sent none keeps the vague
1661
+ // wording, because a made-up wait is the same lie as the network guess this replaced (issue
1662
+ // #3511 — the retry-horizon half; the `code` half landed with #3512).
1663
+ const horizon = retryHorizonOf(result);
1621
1664
  need(`匿名注册没成功:${cause} → 读课程不受影响,但写入类工具(write_lesson)现在用不了;`
1622
- + '稍后重跑本命令就能拿到 token'
1665
+ + (horizon
1666
+ ? `约 ${humanWait(horizon)}后重跑本命令就能拿到 token(服务端给出的重试窗口)`
1667
+ : '稍后重跑本命令就能拿到 token')
1623
1668
  + (serverHint ? `(服务端提示:${serverHint})` : ''));
1624
1669
  return '';
1625
1670
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misaka-net/misakanet-setup",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "MisakaNet failure lessons for your agents: writes an MCP endpoint into each agent config, rules blocks where the client takes one, and a Claude Code hook. At most one anonymous request leaves this machine at install; then only the agent's calls — unless MISAKANET_HOOK_FETCH=1 (off by default) the hook sends the failed call's error text or command.",
5
5
  "bin": {
6
6
  "misakanet-setup": "bin/misakanet-setup.mjs"