dsh-agy-link 0.4.15 → 0.4.16
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 +10 -0
- package/dist/client.js +11 -1
- package/dist/index.js +41 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.16 (2026-08-24)
|
|
4
|
+
|
|
5
|
+
- **External Re-Login Sync (换号自动/手动同步).**
|
|
6
|
+
- **Root cause fixed**: after `agy logout` + re-login as a DIFFERENT account, the pool slot kept the old account's email, cooldowns, quotas and `auth_required` quarantine — and poll gating (0.4.15) then skipped the flagged slot forever, freezing the UI on the stale account.
|
|
7
|
+
- **`resetAccountIdentity`**: detecting a changed email (from local CLI logs, zero network) now resets all identity-bound state (cooldowns / quotas / auth quarantine) while keeping slot config — the new account starts clean instead of inheriting the old one's restrictions.
|
|
8
|
+
- **Zero-network reconciliation before poll gating**: the background poller pre-checks flagged slots via local log scan, so an external re-login self-heals within one poll cycle (≤15 min) without any extra request to Google. Manual click is instant.
|
|
9
|
+
- **Auth self-heal on success**: a successful authenticated quota fetch clears a stale `auth_required` flag (the old token's invalid_grant no longer condemns the new login).
|
|
10
|
+
- **Manual refresh upgrades**: the 刷新 button now also re-reads the model catalog (one `agy models` spawn per explicit click only — new subscription tier may expose different models), and every account card gains a 同步 button for single-account refresh.
|
|
11
|
+
- **Primary slot always re-bootstrapped (real root cause of "UI stuck on old account")**: the primary slot was only created for an EMPTY pool, so once deleted it never came back while other accounts remained — the system-HOME login (e.g. after `agy logout` + re-login) had no slot to attach to and the UI kept showing an isolated account as 主账号. The slot is now recreated at the front on every load; deleting it no longer promotes an isolated account to primary. Disable the slot instead of deleting if unwanted.
|
|
12
|
+
|
|
3
13
|
## 0.4.15 (2026-08-24)
|
|
4
14
|
|
|
5
15
|
- **Quota Polling Risk-Exposure Minimization (root-cause follow-up).**
|
package/dist/client.js
CHANGED
|
@@ -1140,7 +1140,17 @@ body.dark,
|
|
|
1140
1140
|
gap: "3px"
|
|
1141
1141
|
},
|
|
1142
1142
|
onClick: () => toggleExpand(acc.id)
|
|
1143
|
-
}, isExpanded ? [uiIcon("chevronUp", 11), " 收起"] : [uiIcon("chevronDown", 11), " 明细"]) : null,
|
|
1143
|
+
}, isExpanded ? [uiIcon("chevronUp", 11), " 收起"] : [uiIcon("chevronDown", 11), " 明细"]) : null, h("button", {
|
|
1144
|
+
type: "button",
|
|
1145
|
+
className: "agy-btn",
|
|
1146
|
+
style: {
|
|
1147
|
+
...S.btnSm,
|
|
1148
|
+
gap: "4px"
|
|
1149
|
+
},
|
|
1150
|
+
title: "刷新此账号(换号后点这里立即同步 email / 额度 / 模型)",
|
|
1151
|
+
disabled: isBusy,
|
|
1152
|
+
onClick: () => void refreshQuota(acc.id)
|
|
1153
|
+
}, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), "同步中"] : [uiIcon("refresh", 11), " 同步"]), !isPrimary ? h("button", {
|
|
1144
1154
|
type: "button",
|
|
1145
1155
|
className: "agy-btn",
|
|
1146
1156
|
style: {
|
package/dist/index.js
CHANGED
|
@@ -27082,7 +27082,7 @@ var AccountPoolManager = class {
|
|
|
27082
27082
|
* and signed in via /agy add-account.
|
|
27083
27083
|
*/
|
|
27084
27084
|
bootstrapDefaultAccount() {
|
|
27085
|
-
if (this.data.accounts.
|
|
27085
|
+
if (this.data.accounts.some((a) => a.systemHome)) return;
|
|
27086
27086
|
const primary = {
|
|
27087
27087
|
id: "acc_primary",
|
|
27088
27088
|
alias: "主账号 (系统登录)",
|
|
@@ -27093,7 +27093,7 @@ var AccountPoolManager = class {
|
|
|
27093
27093
|
cooldowns: {},
|
|
27094
27094
|
quotas: {}
|
|
27095
27095
|
};
|
|
27096
|
-
this.data.accounts.
|
|
27096
|
+
this.data.accounts.unshift(primary);
|
|
27097
27097
|
this.data.primaryAccountId = primary.id;
|
|
27098
27098
|
this.persist();
|
|
27099
27099
|
}
|
|
@@ -27243,7 +27243,7 @@ var AccountPoolManager = class {
|
|
|
27243
27243
|
force: true
|
|
27244
27244
|
});
|
|
27245
27245
|
} catch {}
|
|
27246
|
-
if (this.data.primaryAccountId === id) this.data.primaryAccountId =
|
|
27246
|
+
if (this.data.primaryAccountId === id) this.data.primaryAccountId = void 0;
|
|
27247
27247
|
if (this.data.activeAccountIds) {
|
|
27248
27248
|
for (const [fam, accId] of Object.entries(this.data.activeAccountIds)) if (accId === id) delete this.data.activeAccountIds[fam];
|
|
27249
27249
|
}
|
|
@@ -27284,6 +27284,22 @@ var AccountPoolManager = class {
|
|
|
27284
27284
|
}
|
|
27285
27285
|
this.persist();
|
|
27286
27286
|
}
|
|
27287
|
+
/**
|
|
27288
|
+
* External re-login detected (agy logout + new login): identity-bound
|
|
27289
|
+
* state from the PREVIOUS account (cooldowns, quotas, auth quarantine)
|
|
27290
|
+
* must not leak onto the new one. Resets everything email-bound while
|
|
27291
|
+
* keeping slot config (alias, dir, proxy, enabled).
|
|
27292
|
+
*/
|
|
27293
|
+
resetAccountIdentity(id, newEmail) {
|
|
27294
|
+
const acc = this.getAccount(id);
|
|
27295
|
+
if (!acc) return;
|
|
27296
|
+
acc.email = newEmail;
|
|
27297
|
+
acc.cooldowns = {};
|
|
27298
|
+
acc.quotas = {};
|
|
27299
|
+
delete acc.authRequired;
|
|
27300
|
+
delete acc.authError;
|
|
27301
|
+
this.persist();
|
|
27302
|
+
}
|
|
27287
27303
|
clearAuthRequired(id) {
|
|
27288
27304
|
const acc = this.getAccount(id);
|
|
27289
27305
|
if (!acc) return;
|
|
@@ -27902,20 +27918,16 @@ var QuotaService = class {
|
|
|
27902
27918
|
const detected = detectEmailFromAgyLogs(home);
|
|
27903
27919
|
if (detected) email = detected;
|
|
27904
27920
|
}
|
|
27921
|
+
if (email && email !== account.email) this.pool.resetAccountIdentity(account.id, email);
|
|
27905
27922
|
const accessToken = await this.getValidAccessToken(account);
|
|
27906
|
-
if (!accessToken)
|
|
27907
|
-
if (email && email !== account.email) this.pool.updateAccountQuotas(account.id, account.quotas, email);
|
|
27908
|
-
return null;
|
|
27909
|
-
}
|
|
27923
|
+
if (!accessToken) return null;
|
|
27910
27924
|
const [summary, discovered] = await Promise.all([this.fetchQuotaSummary(accessToken, account.proxyUrl), this.fetchAvailableModels(accessToken, account.proxyUrl)]);
|
|
27911
27925
|
if (!email) {
|
|
27912
27926
|
const info = await this.fetchUserInfo(accessToken, account.proxyUrl);
|
|
27913
27927
|
if (info?.email) email = info.email;
|
|
27914
27928
|
}
|
|
27915
|
-
if (!summary && (!discovered || !discovered.models))
|
|
27916
|
-
|
|
27917
|
-
return null;
|
|
27918
|
-
}
|
|
27929
|
+
if (!summary && (!discovered || !discovered.models)) return null;
|
|
27930
|
+
if (account.authRequired) this.pool.clearAuthRequired(account.id);
|
|
27919
27931
|
const familyQuotas = {};
|
|
27920
27932
|
if (summary && Array.isArray(summary.groups)) for (const group of summary.groups) {
|
|
27921
27933
|
const dName = (group.displayName || "").toLowerCase();
|
|
@@ -27988,9 +28000,25 @@ var QuotaService = class {
|
|
|
27988
28000
|
* auth-quarantined / in cooldown) so the poller never keeps knocking on
|
|
27989
28001
|
* Google endpoints for accounts already known to be limited. Manual force
|
|
27990
28002
|
* refresh from the UI refreshes everything.
|
|
28003
|
+
*
|
|
28004
|
+
* Before gating, a ZERO-NETWORK identity reconciliation runs for flagged
|
|
28005
|
+
* slots: an external `agy logout` + re-login writes the new email into the
|
|
28006
|
+
* newest CLI logs, and detecting that locally lets us reset the stale
|
|
28007
|
+
* quarantine/cooldowns so the slot rejoins polling — no extra request to
|
|
28008
|
+
* Google is made for this check (risk-control neutral).
|
|
27991
28009
|
*/
|
|
27992
28010
|
async refreshAllQuotas(force = false) {
|
|
27993
|
-
|
|
28011
|
+
let accounts = this.pool.getAccounts();
|
|
28012
|
+
if (!force) {
|
|
28013
|
+
const now = Date.now();
|
|
28014
|
+
for (const acc of accounts) {
|
|
28015
|
+
const flagged = acc.authRequired || Object.values(acc.cooldowns).some((cd) => cd && cd.cooldownUntil > now);
|
|
28016
|
+
if (!acc.systemHome || !flagged) continue;
|
|
28017
|
+
const detected = detectEmailFromAgyLogs(acc.systemHome || !acc.dir ? homedir() : acc.dir);
|
|
28018
|
+
if (detected && detected !== acc.email) this.pool.resetAccountIdentity(acc.id, detected);
|
|
28019
|
+
}
|
|
28020
|
+
accounts = accounts.filter(shouldPollAccount);
|
|
28021
|
+
}
|
|
27994
28022
|
await Promise.allSettled(accounts.map((acc) => this.refreshAccountQuota(acc, force)));
|
|
27995
28023
|
}
|
|
27996
28024
|
};
|
|
@@ -28737,6 +28765,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
28737
28765
|
const acc = pool.getAccount(id);
|
|
28738
28766
|
if (acc) await quota.refreshAccountQuota(acc, true);
|
|
28739
28767
|
} else await quota.refreshAllQuotas(true);
|
|
28768
|
+
catalog.forceRefresh().catch(() => void 0);
|
|
28740
28769
|
sendJson(res, 200, {
|
|
28741
28770
|
ok: true,
|
|
28742
28771
|
pool: pool.getPoolData()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.16",
|
|
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",
|