dsh-agy-link 0.4.16 → 0.4.18
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 +16 -0
- package/dist/client.js +3 -3
- package/dist/index.js +34 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.18 (2026-08-25)
|
|
4
|
+
|
|
5
|
+
- **Quota Fallback Never Clobbers Good Data (5h=100%/weekly-missing 根因).**
|
|
6
|
+
- **What happened**: `retrieveUserQuotaSummary` transiently failed (proxy blip) while `fetchAvailableModels` still answered; the per-model fallback then OVERWROTE the stored family entry with a single-window partial shape — weeklyFraction vanished and the 5h row received wrong-window numbers (observed 5h=100%, reset a week out, weekly `—`, while the live API actually reported 5h 84% / weekly 47%).
|
|
7
|
+
- **`mergeFallbackFamilyQuota`**: last-known-good complete family data now always wins over partial fallback; the fallback only fills families with no usable previous entry (first-ever refresh). Verified live: both endpoints answer correctly and a successful summary refresh fully restores the display.
|
|
8
|
+
- Added `scripts/diag-quota.mts` one-shot endpoint probe for future incidents.
|
|
9
|
+
|
|
10
|
+
## 0.4.17 (2026-08-25)
|
|
11
|
+
|
|
12
|
+
- **Ghost-Cooldown & Quota-Display Fix (额度显示 0% 根因).**
|
|
13
|
+
- **What happened**: the UI showed 5h quota as 0% while `agy` reported 98% — the parsed quota data was CORRECT all along, but (a) any active local cooldown forced the 5h bar to render 0%, and (b) the loose rate-limit classifier kept creating ghost cooldowns: it scanned the ENTIRE stdout (model prose mentioning "rate limit"/"quota", hash fragments containing "429") and matched bare keywords, so an unrelated tool/permission error froze a healthy account out of rotation with a 15-minute+ cooldown (captured real reason: `rate limit reached: declaring permissions: cortex tool write_to_file … invalid tool call error`).
|
|
14
|
+
- **Hard vs soft classification**: new `looksLikeHardRateLimit` (RESOURCE_EXHAUSTED / code·status·HTTP 429 / too many requests / individual quota reached / quota exceeded·reached·exhausted / rate limit exceeded·reached·hit) is the ONLY pattern allowed to put an account into cooldown; soft signals (model overloaded / high traffic) still shape the error message but never cool accounts.
|
|
15
|
+
- **Scan scope narrowed**: error classification reads stderr + the result envelope's error field only — stdout (event JSON + model prose) no longer participates.
|
|
16
|
+
- **Honest quota bars**: a local cooldown no longer overwrites the server-reported fraction with 0%; it now appends a `· 本地冷却中` note next to the reset time instead.
|
|
17
|
+
- Regression tests pin the exact incident text (cortex tool permission error) as a non-rate-limit fixture.
|
|
18
|
+
|
|
3
19
|
## 0.4.16 (2026-08-24)
|
|
4
20
|
|
|
5
21
|
- **External Re-Login Sync (换号自动/手动同步).**
|
package/dist/client.js
CHANGED
|
@@ -963,8 +963,8 @@ body.dark,
|
|
|
963
963
|
const info = acc.quotas[familyKey];
|
|
964
964
|
const cd = acc.cooldowns[familyKey];
|
|
965
965
|
const inCooldown = cd && cd.cooldownUntil > Date.now();
|
|
966
|
-
|
|
967
|
-
|
|
966
|
+
const pct5h = typeof info?.remainingFraction === "number" && Number.isFinite(info.remainingFraction) ? Math.max(0, Math.min(100, Math.round(info.remainingFraction * 100))) : -1;
|
|
967
|
+
const cdNote = inCooldown ? " · 本地冷却中" : "";
|
|
968
968
|
const w5h = formatQuotaWindow(info?.resetTime);
|
|
969
969
|
const pctWeekly = typeof info?.weeklyFraction === "number" && Number.isFinite(info.weeklyFraction) ? Math.max(0, Math.min(100, Math.round(info.weeklyFraction * 100))) : -1;
|
|
970
970
|
const wWeekly = formatQuotaWindow(info?.weeklyResetTime);
|
|
@@ -1052,7 +1052,7 @@ body.dark,
|
|
|
1052
1052
|
fontSize: "12.5px",
|
|
1053
1053
|
marginBottom: "5px",
|
|
1054
1054
|
color: "var(--agy-text-primary)"
|
|
1055
|
-
} }, brandIcon(FAMILY_BRAND[familyKey], 14), h("span", null, label)), renderLine("5h 额度", pct5h, c5h, w5h.resetText), renderLine("周额度", pctWeekly, cWeekly, wWeekly.resetText));
|
|
1055
|
+
} }, brandIcon(FAMILY_BRAND[familyKey], 14), h("span", null, label)), renderLine("5h 额度", pct5h, c5h, w5h.resetText ? w5h.resetText + cdNote : cdNote.replace(/^ · /, "")), renderLine("周额度", pctWeekly, cWeekly, wWeekly.resetText));
|
|
1056
1056
|
};
|
|
1057
1057
|
const renderedAccountCards = accounts.map((acc) => {
|
|
1058
1058
|
const isPrimary = acc.id === pool?.primaryAccountId;
|
package/dist/index.js
CHANGED
|
@@ -115,9 +115,25 @@ function extractAuthUrl(text) {
|
|
|
115
115
|
if (!m) return void 0;
|
|
116
116
|
return m[0].replace(/[)\]>.,;\x27\x22]+$/, "");
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* HARD, server-issued rate-limit signatures — the ONLY patterns allowed to
|
|
120
|
+
* put an account into cooldown. Deliberately narrow: bare `429` or
|
|
121
|
+
* `rate limit` matched incidental substrings in the wild (hash/UUID
|
|
122
|
+
* fragments, model prose mentioning quotas, unrelated tool/permission
|
|
123
|
+
* errors quoting such words) and produced ghost cooldowns that froze
|
|
124
|
+
* healthy accounts out of rotation.
|
|
125
|
+
*/
|
|
126
|
+
function looksLikeHardRateLimit(text) {
|
|
127
|
+
if (!text) return false;
|
|
128
|
+
return /RESOURCE_EXHAUSTED|code[ :]?429\b|status[ :]?429\b|HTTP[ :]?429\b|too many requests|individual quota reached|quota (?:exceeded|reached|exhausted)|rate[ -]?limit(?:ed)? (?:exceeded|reached|hit)|exceeded (?:your |the )?quota/i.test(text);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Soft heuristic adds capacity signals (model overloaded / high traffic).
|
|
132
|
+
* Shapes the user-facing error message only — NEVER cools an account down.
|
|
133
|
+
*/
|
|
118
134
|
function looksLikeRateLimit(text) {
|
|
119
135
|
if (!text) return false;
|
|
120
|
-
return
|
|
136
|
+
return looksLikeHardRateLimit(text) || /model overloaded|experiencing high traffic/i.test(text);
|
|
121
137
|
}
|
|
122
138
|
/**
|
|
123
139
|
* Parse reset duration in milliseconds from rate-limit / quota-exhausted error strings.
|
|
@@ -2364,11 +2380,8 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
2364
2380
|
const conversationId = streamCid ?? diffed;
|
|
2365
2381
|
const r = rec.getResultEvent();
|
|
2366
2382
|
const consumable = r !== null && (r.ok || r.response !== "");
|
|
2367
|
-
const
|
|
2368
|
-
|
|
2369
|
-
outcome.stdout,
|
|
2370
|
-
parser.stats.lastResultError
|
|
2371
|
-
].filter(Boolean).join(" "));
|
|
2383
|
+
const rawErrText = [outcome.stderrTail, parser.stats.lastResultError].filter(Boolean).join(" ");
|
|
2384
|
+
const isRateLimit = looksLikeRateLimit(rawErrText);
|
|
2372
2385
|
let failure = null;
|
|
2373
2386
|
if (outcome.aborted) failure = {
|
|
2374
2387
|
kind: "aborted",
|
|
@@ -2423,7 +2436,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
2423
2436
|
}
|
|
2424
2437
|
} else {
|
|
2425
2438
|
const effectiveRateLimit = isRateLimit || looksLikeRateLimit(failure.message);
|
|
2426
|
-
if (account &&
|
|
2439
|
+
if (account && looksLikeHardRateLimit(rawErrText)) this.deps.pool?.recordFailure(account.id, family, failure.message);
|
|
2427
2440
|
if (account && (failure.code === Err.AUTH || /invalid_grant|not signed in|auth/i.test(failure.message))) this.deps.pool?.markAuthRequired(account.id, failure.message);
|
|
2428
2441
|
if (!isAux && sessionAccountKey !== "") {
|
|
2429
2442
|
if (failure.code === Err.AUTH || effectiveRateLimit || failure.message && /conversation.*(not found|invalid|not recognized|expired|does not exist)|session.*(expired|invalid)/i.test(failure.message)) this.deps.store.delete(sessionAccountKey);
|
|
@@ -27668,6 +27681,18 @@ var PoolAuthFlow = class {
|
|
|
27668
27681
|
};
|
|
27669
27682
|
//#endregion
|
|
27670
27683
|
//#region src/host/quota.ts
|
|
27684
|
+
/**
|
|
27685
|
+
* When the quota-summary endpoint transiently fails, per-model fallback
|
|
27686
|
+
* data carries a SINGLE window (sometimes the weekly one) and no weekly
|
|
27687
|
+
* fields. Overwriting a previously COMPLETE family entry with that partial
|
|
27688
|
+
* shape dropped weeklyFraction to none and put wrong-window numbers into
|
|
27689
|
+
* the 5h row (observed: 5h=100% / reset a week out / weekly missing).
|
|
27690
|
+
* Rule: last-known-good complete data always wins over partial fallback.
|
|
27691
|
+
*/
|
|
27692
|
+
function mergeFallbackFamilyQuota(prev, fallback) {
|
|
27693
|
+
if (prev && typeof prev.remainingFraction === "number") return prev;
|
|
27694
|
+
return fallback;
|
|
27695
|
+
}
|
|
27671
27696
|
function detectEmailFromAgyLogs(homeDir) {
|
|
27672
27697
|
const logDir = join(homeDir, ".gemini", "antigravity-cli", "log");
|
|
27673
27698
|
if (!existsSync(logDir)) return void 0;
|
|
@@ -27972,11 +27997,11 @@ var QuotaService = class {
|
|
|
27972
27997
|
remainingFraction: remaining,
|
|
27973
27998
|
resetTime
|
|
27974
27999
|
});
|
|
27975
|
-
if (!familyQuotas[fam]) familyQuotas[fam] = {
|
|
28000
|
+
if (!familyQuotas[fam]) familyQuotas[fam] = mergeFallbackFamilyQuota(account.quotas[fam], {
|
|
27976
28001
|
remainingFraction: remaining,
|
|
27977
28002
|
resetTime,
|
|
27978
28003
|
updatedAt: now
|
|
27979
|
-
};
|
|
28004
|
+
});
|
|
27980
28005
|
else if (familyQuotas[fam].remainingFraction === void 0) {
|
|
27981
28006
|
const curRemaining = familyQuotas[fam].remainingFraction ?? 1;
|
|
27982
28007
|
if (remaining < curRemaining) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.18",
|
|
4
4
|
"description": "Google Antigravity (agy CLI) models for DeepSeek Harness — stream Gemini/Claude/GPT-OSS subscriptions into DSH with thinking, tool activity, token usage and in-GUI Google OAuth login.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|