dsh-agy-link 0.4.19 → 0.4.21
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 +18 -0
- package/dist/index.js +46 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.21 (2026-08-27)
|
|
4
|
+
|
|
5
|
+
- **Fixed: Silent `agy exited with code 1` Hid the Real Cause (发送消息无回复、只见 exit 1).**
|
|
6
|
+
- **Field report**: a user session log showed every turn failing with the bare message `agy exited with code 1` (code PROCESS_EXIT, empty stderr, zero tokens) — each attempt ran ~7–10s, failed once, retried once, failed again, and the UI never surfaced WHY (no reply text at all).
|
|
7
|
+
- **Root cause of the blindness (verified live against agy 1.1.22)**: agy reports its failure as a `result` envelope on **stdout** (`{"event":"result",...,"status":"ERROR","error":"<human-readable reason>"}`) and exits 1 with EMPTY stderr — e.g. an invalid model/effort pairing fails instantly with `--model gemini-3.7-flash requires --effort (available: low, medium, high)`. The adapter's non-zero-exit branch dropped that envelope entirely: rate-limit-shaped errors were still classified (the classifier already read `lastResultError`), but any OTHER failure degraded to the bare exit-code line with no cause.
|
|
8
|
+
- **Fix**: the PROCESS_EXIT failure message now prefers the stdout envelope's error text (falling back to the stderr tail). Same error code and retry policy; users finally see agy's own reason, e.g. `agy exited with code 1: upstream request failed while generating` or `…: --model gemini-3.7-flash requires --effort (available: low, medium, high)`.
|
|
9
|
+
- **Diagnostics unaffected**: `/agy doctor` (`~/.dsh/agy-link/diagnostics/doctor-*.md`) still carries the full redacted raw stdout tail for deeper incidents.
|
|
10
|
+
- Regression test pins the exact silent-failure shape (stdout envelope + exit 1 + empty stderr) via a new `exit-error` fake-agy mode.
|
|
11
|
+
|
|
12
|
+
## 0.4.20 (2026-08-27)
|
|
13
|
+
|
|
14
|
+
- **Fixed: Primary Account Quota Fetched With a STALE Token (主账号刷新/同步拿的值不对).**
|
|
15
|
+
- **Root cause (verified live)**: on macOS, agy >= 1.1.15 keeps its CURRENT credential in the Keychain; the on-disk `antigravity-oauth-token` was a stale leftover from a PREVIOUS account's login. `getStoredToken` read the disk file FIRST and only fell back to the Keychain — so every 刷新/同步 used the old account's (still-valid) token and displayed ITS quota under the current login's name (observed: agy authenticated as q98… while the disk file still held elegantmanco's token; UI showed the wrong account's 5h/weekly numbers).
|
|
16
|
+
- **Keychain-first token resolution**: for the primary / system-HOME account the Keychain credential now WINS over the on-disk file (disk remains the fallback for older agy builds and non-mac systems). Isolated pool accounts are pinned by test to never touch the shared Keychain. Verified end-to-end against live Google endpoints: the primary's token identity and quota now match the actual agy login (5h 91% / weekly 31% instead of the stale account's numbers).
|
|
17
|
+
- **Token-anchored identity on manual refresh**: `refreshAccountQuota(force=true)` additionally calls the OAuth userinfo endpoint once per explicit user click and re-labels the slot to the token's TRUE owner (`resetAccountIdentity`), so an external `agy logout` + re-login self-heals in one click even when logs disagree.
|
|
18
|
+
- **Log detection hardened**: `detectEmailFromAgyLogs` returns the LAST match per file (append-ordered logs → newest login wins), fixing first-match returning a superseded account.
|
|
19
|
+
- **Risk posture preserved**: background polls (force=false) still NEVER call userinfo — zero extra network on the automatic path (pinned by a regression test).
|
|
20
|
+
|
|
3
21
|
## 0.4.19 (2026-08-26)
|
|
4
22
|
|
|
5
23
|
- **Fixed: Antigravity Models Missing From the Model Picker (登录成功、额度正常但模型列表为空 — issue #1).**
|
package/dist/index.js
CHANGED
|
@@ -2430,12 +2430,14 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
2430
2430
|
message: "Google Antigravity quota / rate limit reached: " + bestMsg
|
|
2431
2431
|
};
|
|
2432
2432
|
} else if (!consumable) {
|
|
2433
|
-
if (outcome.code !== 0)
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2433
|
+
if (outcome.code !== 0) {
|
|
2434
|
+
const detail = parser.stats.lastResultError ?? (outcome.stderrTail !== "" ? brief(outcome.stderrTail) : "");
|
|
2435
|
+
failure = {
|
|
2436
|
+
kind: "error",
|
|
2437
|
+
code: Err.PROCESS_EXIT,
|
|
2438
|
+
message: "agy exited with code " + outcome.code + (detail !== "" ? ": " + detail : "")
|
|
2439
|
+
};
|
|
2440
|
+
} else if (parser.stats.lastResultError) failure = {
|
|
2439
2441
|
kind: "error",
|
|
2440
2442
|
code: Err.AGY_ERROR,
|
|
2441
2443
|
message: "agy reported an error: " + parser.stats.lastResultError
|
|
@@ -27726,8 +27728,11 @@ function detectEmailFromAgyLogs(homeDir) {
|
|
|
27726
27728
|
time: statSync(join(logDir, f)).mtimeMs
|
|
27727
27729
|
})).sort((a, b) => b.time - a.time).slice(0, 5);
|
|
27728
27730
|
for (const file of files) try {
|
|
27729
|
-
const
|
|
27730
|
-
|
|
27731
|
+
const content = readFileSync(join(logDir, file.name), "utf8");
|
|
27732
|
+
const re = /(?:authenticated successfully as|applyAuthResult:\s*email=|"email"\s*:\s*"|User:\s*)\s*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/gi;
|
|
27733
|
+
let last;
|
|
27734
|
+
for (let m = re.exec(content); m !== null; m = re.exec(content)) if (m[1]) last = m[1];
|
|
27735
|
+
if (last) return last;
|
|
27731
27736
|
} catch {}
|
|
27732
27737
|
} catch {}
|
|
27733
27738
|
}
|
|
@@ -27814,21 +27819,39 @@ var QuotaService = class {
|
|
|
27814
27819
|
return join(home, ".gemini", "antigravity-cli", "antigravity-oauth-token");
|
|
27815
27820
|
}
|
|
27816
27821
|
/**
|
|
27822
|
+
* Read the system-HOME Keychain credential. Protected so tests (and future
|
|
27823
|
+
* platforms) can substitute the reader without touching the real Keychain.
|
|
27824
|
+
*/
|
|
27825
|
+
readSystemKeychainToken() {
|
|
27826
|
+
return readMacKeychainToken();
|
|
27827
|
+
}
|
|
27828
|
+
/**
|
|
27817
27829
|
* Read the active token document and normalize it to a flat StoredToken.
|
|
27818
|
-
*
|
|
27819
|
-
*
|
|
27830
|
+
*
|
|
27831
|
+
* Precedence for the primary / system-HOME account: the macOS Keychain
|
|
27832
|
+
* WINS over the on-disk token file. agy >= 1.1.15 keeps its CURRENT
|
|
27833
|
+
* credential in the Keychain; the on-disk antigravity-oauth-token can be a
|
|
27834
|
+
* stale leftover from a PREVIOUS account's login (verified live: disk
|
|
27835
|
+
* held an old account's token while agy itself was authenticated as
|
|
27836
|
+
* someone else — disk-first precedence made every quota refresh fetch the
|
|
27837
|
+
* WRONG account's numbers). Isolated pool accounts only ever read their
|
|
27838
|
+
* own directory's file; the Keychain is one shared slot they must not see.
|
|
27820
27839
|
*/
|
|
27821
27840
|
getStoredToken(account) {
|
|
27822
|
-
const
|
|
27823
|
-
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27841
|
+
const disk = (() => {
|
|
27842
|
+
const file = this.getTokenFilePath(account);
|
|
27843
|
+
if (!existsSync(file)) return null;
|
|
27844
|
+
try {
|
|
27845
|
+
const tok = normalizeStoredToken(JSON.parse(readFileSync(file, "utf8")));
|
|
27846
|
+
if (tok && (tok.accessToken || tok.refreshToken)) return tok;
|
|
27847
|
+
} catch {}
|
|
27848
|
+
return null;
|
|
27849
|
+
})();
|
|
27827
27850
|
if (account.systemHome || !account.dir) {
|
|
27828
|
-
const keychainToken =
|
|
27829
|
-
if (keychainToken) return keychainToken;
|
|
27851
|
+
const keychainToken = this.readSystemKeychainToken();
|
|
27852
|
+
if (keychainToken && (keychainToken.accessToken || keychainToken.refreshToken)) return keychainToken;
|
|
27830
27853
|
}
|
|
27831
|
-
return
|
|
27854
|
+
return disk;
|
|
27832
27855
|
}
|
|
27833
27856
|
/** Persist refreshed tokens back in the SAME on-disk shape agy wrote. */
|
|
27834
27857
|
persistRefreshedToken(account, tokens) {
|
|
@@ -27967,9 +27990,13 @@ var QuotaService = class {
|
|
|
27967
27990
|
const detected = detectEmailFromAgyLogs(home);
|
|
27968
27991
|
if (detected) email = detected;
|
|
27969
27992
|
}
|
|
27970
|
-
if (email && email !== account.email) this.pool.resetAccountIdentity(account.id, email);
|
|
27971
27993
|
const accessToken = await this.getValidAccessToken(account);
|
|
27972
27994
|
if (!accessToken) return null;
|
|
27995
|
+
if (force) {
|
|
27996
|
+
const info = await this.fetchUserInfo(accessToken, account.proxyUrl);
|
|
27997
|
+
if (info?.email) email = info.email;
|
|
27998
|
+
}
|
|
27999
|
+
if (email && email !== account.email) this.pool.resetAccountIdentity(account.id, email);
|
|
27973
28000
|
const [summary, discovered] = await Promise.all([this.fetchQuotaSummary(accessToken, account.proxyUrl), this.fetchAvailableModels(accessToken, account.proxyUrl)]);
|
|
27974
28001
|
if (!email) {
|
|
27975
28002
|
const info = await this.fetchUserInfo(accessToken, account.proxyUrl);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
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",
|