dsh-xdpool 2.6.0 → 2.6.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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-xdpool",
3
3
  "displayName": "DSH XD Pool",
4
4
  "description": "One DSH model pool for every free quota you have: merge all locally signed-in WorkBuddy accounts with auto-failover, sign in to 13 vendors by QR/web, aggregate 76 vendors' free tiers, plus a usage dashboard, model routing, a local OpenAI-compatible gateway and a points-automation suite.",
5
- "version": "2.6.0",
5
+ "version": "2.6.2",
6
6
  "license": "MIT",
7
7
  "author": "XDTrees",
8
8
  "homepage": "https://github.com/XDTrees/dsh-xdpool#readme",
@@ -1221,7 +1221,22 @@ async function requestToken(body, keyPair, fetcher = fetch) {
1221
1221
  if (!response.ok || !data?.credentials) {
1222
1222
  const message2 = `CodeArts token request failed: ${response.status}${data ? ` ${JSON.stringify(data)}` : ""}`;
1223
1223
  const errorCode = String(data?.error_code ?? "");
1224
- if (data?.error === "invalid_grant" || errorCode.includes("ExpiredRefreshToken")) {
1224
+ if (data?.error === "invalid_grant" || errorCode.includes("ExpiredRefreshToken")
1225
+ // [xdpool-local] a consumed refresh token is a dead credential.
1226
+ //
1227
+ // Huawei STS refresh tokens are SINGLE-USE: presenting one returns a
1228
+ // new one, and the old value is marked used. "Has been used" is
1229
+ // therefore terminal — either two refreshes raced and this is the
1230
+ // loser, or a successful refresh failed to persist its replacement.
1231
+ // Reporting it as an ordinary error left the account in the pool, so
1232
+ // every later request paid for another hopeless refresh and the user
1233
+ // saw an opaque 400 instead of "sign in again".
1234
+ //
1235
+ // Scoped here rather than in the shared predicate on purpose:
1236
+ // STS5.* is Huawei's namespace, and another vendor could reuse a
1237
+ // similar code for something recoverable.
1238
+ || errorCode.includes("STS5.1806")
1239
+ || /refresh token has been used/iu.test(String(data?.error_msg ?? ""))) {
1225
1240
  throw new RefreshTokenExpiredError(message2);
1226
1241
  }
1227
1242
  throw new Error(message2);
@@ -58010,6 +58025,21 @@ function parseCreditBalance(metrics) {
58010
58025
  );
58011
58026
  return { total, packages, expiredTotal: 0 };
58012
58027
  }
58028
+ // [xdpool-local] Whether this CodeArts credential's own STS expiry has passed.
58029
+ //
58030
+ // Huawei mints these AK/SK/security-token triples with a two-hour life, so a
58031
+ // stored credential very often IS expired while its refresh_token still has
58032
+ // weeks left. The gateway answers that with
58033
+ // `APIG.0602 Bad request: the security token has expired` — accurate, but it
58034
+ // reads like a sign-in failure and sends people off to re-scan a QR code they
58035
+ // never needed to re-scan. Callers use this to name the condition, and to
58036
+ // decide whether a re-mint is worth trying before reporting it at all.
58037
+ function codeartsCredentialExpired(credential) {
58038
+ const raw = credential === null || credential === void 0 ? "" : credential.expires_at;
58039
+ if (typeof raw !== "string" || raw.length === 0) return false;
58040
+ const parsed = Date.parse(raw);
58041
+ return Number.isFinite(parsed) ? parsed <= Date.now() : false;
58042
+ }
58013
58043
  async function fetchCodeArtsAccountInfoDetailed(credential, fetcher = fetch) {
58014
58044
  const url = `${CODEARTS_SNAP_ENGINE_URL}${CODEARTS_PACKAGE_INFO_PATH}`;
58015
58045
  const result = await signedSnapRequest("GET", url, credential, void 0, fetcher);
@@ -60762,6 +60792,34 @@ async function collectClaimResults(accounts, product, deps) {
60762
60792
  }
60763
60793
  return { results, summary: computeClaimSummary(outcomes) };
60764
60794
  }
60795
+ // [xdpool-local] Re-mint any CodeArts credential whose own STS expiry has
60796
+ // already passed, before the caller reports it as a failure.
60797
+ //
60798
+ // Best-effort by design: every failure is logged and swallowed. A refresh that
60799
+ // was rejected (`invalid_grant` — the token was rotated away by another client)
60800
+ // must still let the caller run, because the resulting balance error is the only
60801
+ // place that can tell the user to sign in again.
60802
+ //
60803
+ // It goes through `refreshAccountCredential`, which is the same per-ref serial
60804
+ // queue the 30-minute scheduler uses, so an on-demand heal can never race the
60805
+ // scheduler into consuming one refresh_token twice.
60806
+ async function healExpiredCodeArtsCredentials(accounts, pool, auth, resolve, warn) {
60807
+ if (auth === void 0 || pool === void 0) return;
60808
+ for (const entry of accounts) {
60809
+ if (entry.enabled === false) continue;
60810
+ try {
60811
+ const resolved = await resolve(credentialRef13(entry.credentialRef));
60812
+ if (resolved === void 0) continue;
60813
+ const credential = JSON.parse(resolved.value);
60814
+ if (!codeartsCredentialExpired(credential)) continue;
60815
+ if (!isCodeArtsRefreshable(credential)) continue;
60816
+ warn?.(`[channel-pack] codearts 账号 ${entry.id} 的临时凭证已过期,先自动续期`);
60817
+ await auth.refreshAccountCredential(entry.credentialRef, pool, entry.id);
60818
+ } catch (error) {
60819
+ warn?.(`[channel-pack] codearts 账号 ${entry.id} 自动续期失败(仍继续查询):${String(error)}`);
60820
+ }
60821
+ }
60822
+ }
60765
60823
  async function collectCreditBalances(accounts, product, deps) {
60766
60824
  const fetcher = deps.fetcher ?? fetch;
60767
60825
  const fetchBalance = deps.fetchBalance ?? (async (credential, product2) => fetchCreditBalance(
@@ -61116,6 +61174,30 @@ function registerChannelPackEndpoints(ctx, pool, codearts, buddy, workbuddy, lob
61116
61174
  };
61117
61175
  }
61118
61176
  async function handleMethod(method, payload, _signal) {
61177
+ // [xdpool-local] One heal for every CodeArts entry point, before the switch.
61178
+ //
61179
+ // A CodeArts credential lives two hours; its refresh_token behind it lives
61180
+ // ~30 days. Three separate RPC families read that credential and each of
61181
+ // them can be the FIRST call after it has run out: `credits.balances` (the
61182
+ // panel opening), `credits.status` and `credits.claimAll` (the 09:00 daily
61183
+ // check-in, which then fails silently in a scheduler log nobody reads).
61184
+ // Patching one branch would have left the other two reporting a dead
61185
+ // sign-in for a credential that one refresh fixes, so the heal sits here,
61186
+ // on the path they all share. It is idempotent and cheap when healthy:
61187
+ // nothing is refreshed unless the credential's own stamp is already past.
61188
+ if (typeof payload === "object" && payload !== null && payload.provider === "codearts") {
61189
+ try {
61190
+ await healExpiredCodeArtsCredentials(
61191
+ await pool.listAccounts("codearts"),
61192
+ pool,
61193
+ codearts,
61194
+ (ref) => ctx.credentials.resolve(ref),
61195
+ (msg) => ctx.logger?.warn?.(msg)
61196
+ );
61197
+ } catch (error) {
61198
+ ctx.logger?.warn?.(`[channel-pack] codearts 凭证对账失败(继续处理该请求):${String(error)}`);
61199
+ }
61200
+ }
61119
61201
  if (method.startsWith("opencode.")) {
61120
61202
  if (config.disableOpencode === true) {
61121
61203
  return { ok: false, error: { code: "provider-disabled", message: "OpenCode account integration is disabled in this host" } };
@@ -62219,7 +62301,14 @@ function registerChannelPackEndpoints(ctx, pool, codearts, buddy, workbuddy, lob
62219
62301
  resolve: (ref) => ctx.credentials.resolve(ref),
62220
62302
  fetchBalanceDetailed: async (credential) => {
62221
62303
  const result = await fetchCodeArtsAccountInfoDetailed(credential);
62222
- if (!result.ok) return { balance: null, error: `\u8D26\u6237\u4FE1\u606F\u67E5\u8BE2\u5931\u8D25\uFF1A${result.message}` };
62304
+ if (!result.ok) {
62305
+ // [xdpool-local] An expired STS token is the commonest CodeArts
62306
+ // failure, and the raw APIG message does not say what to do about
62307
+ // it. Name the condition, then name the one action that fixes it.
62308
+ // Only when it IS expired: a 5xx keeps its original wording.
62309
+ const advice = codeartsCredentialExpired(credential) ? "\uFF08\u4E34\u65F6\u51ED\u8BC1\u5DF2\u8FC7\u671F\uFF0C\u70B9\u300C\u5237\u65B0\u51ED\u636E\u300D\u5373\u53EF\u6362\u65B0\uFF0C\u65E0\u9700\u91CD\u65B0\u767B\u5F55\uFF09" : "";
62310
+ return { balance: null, error: `\u8D26\u6237\u4FE1\u606F\u67E5\u8BE2\u5931\u8D25${advice}\uFF1A${result.message}` };
62311
+ }
62223
62312
  const info = result.info;
62224
62313
  if (!info.isCreditPackage) {
62225
62314
  return {