@eddyskywalker/dsh-chatgpt-subscription 0.36.1 → 0.36.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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.36.2 - 2026-10-22
6
+ - **[claude] 设置卡不再拿一枚已死的 access token 请求上游:status / 额度 / 测试连接 / 目录刷新统一走号池续期,托管账号闲置一夜后不再诱导用户重新登录(#66)**
7
+ - **现象**:Claude 订阅的 access token 只有约 8 小时寿命。卡片上的「令牌到期」、额度读取与「测试连接」在托管(managed)账号闲置一夜后全部出错——到期时间显示为过去时间、额度区报错、连接测试失败,看起来像「登录每天失效」,用户于是每天重新登录一次;而重登只是把到期时间刷成新的 8 小时,第二天照旧。
8
+ - **根因**:续期只发生在模型请求路径(`adapter` → `pool.getEffectiveAccount()` → `freshAccount()`,由 `claudeNeedsRefresh()` 在到期前 5 分钟内触发)。`/claude/api` 的 `status`、`quota`、`connection/test`、`catalog/refresh` 则直接用 `activeAccount()` 从号池读出的**存储值**请求上游,从不续期:卡片永远只看到「最后一枚被存下来的 access token」,8 小时后必然过期。相邻的 Kimi Code 线路早有 `effectiveCredentials()` 解决此事,Claude 线路缺这一段。
9
+ - **改法**:`registerClaudeRoutes()` 新增与 Kimi 同形的 `effectiveCredentials(target)` —— 无 pool 或无 `target.id` 时返回存储凭据,否则经 `pool.getFreshCredential(target.id, fetchFn)` 续期,续期失败时回落存储凭据(卡片仍能渲染账号列表,真实原因由那次请求报告)。四条卡片路径统一改用它;`status` 先续期、再读额度、再构建响应,所以 `accounts[].expiresAt`(读自号池行,续期会写回)就是续期后的新值。`readStatus()` 增加可选第二参,让额度读取、catalog 加载与 `accounts[]` 描述同一枚已消费的凭据。
10
+ - **不变量**:adopted(Claude Code 快照)仍然永不刷新——号池对 adopted 恒不续期、并在发网络请求前就拒绝,正好落进上面的回落分支,卡片只展示快照自身的 `authStatus`;只有续期被上游**拒绝**(`refreshFailureStatus` 仅对 `ClaudeUnauthorizedError` 写入)时,账号行才显示「需重新登录」,临时网络失败不会把账号标死。单飞由号池内核 `freshAccount()` 负责,同一枚 refresh token 不会被花两次,路由层没有另做互斥。
11
+ - **测试**:`test/claude-routes.test.ts` 新增 `describe('credential renewal on the card paths')`(6 例,注入假 refresh seam 与记录 `Authorization` 的 fetch double,全程无网络)——`GET /status` 恰好续期 1 次且池内 `expiresAt` 变未来、不写 `authStatus`、usage 请求带新 bearer、`accounts[].expiresAt` 与池内一致、`quotaError: null`;`POST /quota` 同理;`connection/test` 与 `catalog/refresh` 分别以新 bearer 请求 `/v1/messages` 与 `/v1/models`;过期 adopted 快照刷新 0 次且仍渲染 `authStatus='expired'`;卡片与模型请求并发、两次卡片轮询并发都只续期一次。反向验证:把 `routes.ts` 退回基线后 6 条中 5 条失败(adopted 那条按设计通过)。
12
+ - **验证**:`npm run typecheck` 三个工程 0 错误;全量 `npm test` 275 文件通过 / 1 跳过,4477 passed / 7 skipped / 0 failed;`npm run build` 通过。
13
+ - **已知边界**:卡片路径的续期与模型请求共用同一续期入口,因此一次续期失败(例如离线)会让下一次状态轮询再次尝试续期;这与模型请求路径的既有行为一致,未在路由层另加冷却。
5
14
  ## 0.36.1 - 2026-10-22
6
15
  - **[cline-pass] 额度不再只在手动刷新时动:状态读取按 TTL 在答背后刷新,设置卡与输入框徽标 60 秒自动更新**
7
16
  - **现象**:Cline Pass 设置页打开后额度数字就定住了——同一次会话里消耗掉的部分,要重新打开页面或点一次「刷新额度」才会反映;输入框旁的额度徽标同样停在首次读到的数字。
package/lib/index.js CHANGED
@@ -44615,6 +44615,34 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44615
44615
  };
44616
44616
  };
44617
44617
  /**
44618
+ * The credential the card should spend right now, renewed when a pool is in
44619
+ * charge.
44620
+ *
44621
+ * Every card path below (status, quota, the connection probe, the catalog
44622
+ * refresh) used to hand out {@link activeAccount}'s STORED value. An access
44623
+ * token lives for hours, not days, so a managed account left idle overnight
44624
+ * presented a token that had already died: the usage read and the probe were
44625
+ * answered 401, and the card rendered that as a credential the user had to
44626
+ * replace by signing in again — for a refresh token that was still good, and
44627
+ * for a model request path that renews by itself.
44628
+ *
44629
+ * The pool owns both the rotation and the write-back, so asking it here also
44630
+ * updates the expiry the card displays (the account list is read from the pool
44631
+ * rows). Without a pool there is no rotation to consult: the single-credential
44632
+ * store keeps its own behaviour. A borrowed Claude Code snapshot keeps its
44633
+ * never-refresh invariant for free — the pool's own guard refuses it before a
44634
+ * request is made — and simply answers with the stored credential.
44635
+ */
44636
+ const effectiveCredentials = async (target) => {
44637
+ if (pool === void 0) return target.credentials;
44638
+ if (target.id === void 0) return target.credentials;
44639
+ try {
44640
+ return await pool.getFreshCredential(target.id, fetchFn);
44641
+ } catch {
44642
+ return target.credentials;
44643
+ }
44644
+ };
44645
+ /**
44618
44646
  * The store the sign-in flow writes through.
44619
44647
  *
44620
44648
  * With a pool the write goes through the pool, because the pool is the routing
@@ -44648,7 +44676,7 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44648
44676
  ...options.login ?? {},
44649
44677
  fetchFn
44650
44678
  };
44651
- const readStatus = (quotaError = null) => activeAccount().then((active) => getClaudeWebStatus(store, modelSettings, preferences, options, quotaError, active));
44679
+ const readStatus = (quotaError = null, resolved) => (resolved === void 0 ? activeAccount() : Promise.resolve(resolved)).then((active) => getClaudeWebStatus(store, modelSettings, preferences, options, quotaError, active));
44652
44680
  return ctx.webServer.register({
44653
44681
  kind: "prefix",
44654
44682
  path: ROUTE_PREFIX,
@@ -44658,7 +44686,11 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44658
44686
  try {
44659
44687
  if (path === "" || path === "status") {
44660
44688
  if (method !== "GET") return sendMethodNotAllowed$5(response);
44661
- const active = await activeAccount();
44689
+ const stored = await activeAccount();
44690
+ const active = {
44691
+ id: stored.id,
44692
+ credentials: await effectiveCredentials(stored)
44693
+ };
44662
44694
  const cached = active.credentials === void 0 ? null : cachedQuotaForPool(active.id, active.credentials);
44663
44695
  const stale = cached === null || Date.now() - (cached.fetchedAt || 0) > 12e4;
44664
44696
  if (active.credentials !== void 0 && stale) {
@@ -44674,7 +44706,7 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44674
44706
  return sendJson$5(response, 200, {
44675
44707
  ok: true,
44676
44708
  value: {
44677
- ...await readStatus(quotaRefresh.lastError()),
44709
+ ...await readStatus(quotaRefresh.lastError(), active),
44678
44710
  quotaRefreshing: quotaRefresh.refreshing
44679
44711
  }
44680
44712
  });
@@ -44743,11 +44775,12 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44743
44775
  error: "Cross-origin request rejected."
44744
44776
  });
44745
44777
  const active = await activeAccount();
44746
- if (active.credentials === void 0) return sendJson$5(response, 400, {
44778
+ const credentials = active.credentials === void 0 ? void 0 : await effectiveCredentials(active);
44779
+ if (credentials === void 0) return sendJson$5(response, 400, {
44747
44780
  ok: false,
44748
44781
  error: "Not signed in."
44749
44782
  });
44750
- const probe = await probeConnection(active.credentials, { fetchFn });
44783
+ const probe = await probeConnection(credentials, { fetchFn });
44751
44784
  return sendJson$5(response, 200, {
44752
44785
  ok: true,
44753
44786
  value: probe.ok ? {
@@ -44778,11 +44811,11 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44778
44811
  error: "Cross-origin request rejected."
44779
44812
  });
44780
44813
  const active = await activeAccount();
44781
- if (active.credentials === void 0) return sendJson$5(response, 400, {
44814
+ const credentials = active.credentials === void 0 ? void 0 : await effectiveCredentials(active);
44815
+ if (credentials === void 0) return sendJson$5(response, 400, {
44782
44816
  ok: false,
44783
44817
  error: "Not signed in to Claude."
44784
44818
  });
44785
- const credentials = active.credentials;
44786
44819
  let quotaError = null;
44787
44820
  try {
44788
44821
  await fetchAccountQuota$1(credentials, {
@@ -44807,13 +44840,11 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
44807
44840
  });
44808
44841
  clearCachedCatalog();
44809
44842
  const active = await activeAccount();
44810
- if (active.credentials !== void 0) {
44811
- const credentials = active.credentials;
44812
- await (options.loadCatalog ?? ((current) => loadCatalog(current, {
44813
- fetchFn,
44814
- force: true
44815
- })))(credentials).catch(() => void 0);
44816
- }
44843
+ const credentials = active.credentials === void 0 ? void 0 : await effectiveCredentials(active);
44844
+ if (credentials !== void 0) await (options.loadCatalog ?? ((current) => loadCatalog(current, {
44845
+ fetchFn,
44846
+ force: true
44847
+ })))(credentials).catch(() => void 0);
44817
44848
  return sendJson$5(response, 200, {
44818
44849
  ok: true,
44819
44850
  value: await readStatus()
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/host/claude/routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/host/claude/routes.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eddyskywalker/dsh-chatgpt-subscription",
3
- "version": "0.36.1",
3
+ "version": "0.36.2",
4
4
  "description": "DSH provider plugin for Codex access through a ChatGPT subscription.",
5
5
  "icon": "./icon.svg",
6
6
  "author": {