dsh-agy-link 0.4.38 → 0.4.41
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 +50 -0
- package/dist/client.js +14 -1
- package/dist/index.js +373 -84
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.41 (2026-09-30)
|
|
4
|
+
|
|
5
|
+
### English
|
|
6
|
+
|
|
7
|
+
- **Fix: the UI Stop button could not kill an offline wedged run.** `killTree` sent a single SIGTERM to the process group; agy's graceful-shutdown path ignores it while the process is wedged in offline dial retries (verified live: the run survived `kill <pid>` and only died on `kill -9`). The runner now escalates to SIGKILL for the whole group 3s after SIGTERM, so Stop always wins.
|
|
8
|
+
- **Fix: offline runs spun on "thinking" forever.** The idle watchdog treated stderr chatter as activity — agy logs a dial-retry line to stderr every ~35s when it cannot reach Google, so a run that never emitted one stdout event stayed "active" until the 240m print-timeout. The watchdog is now **stdout-only**; login flows opt back in via the new `stderrKeepalive` run option (a browser login is legitimately stdout-silent for minutes). A timeout with zero stdout events now reports the likely cause: the run cannot reach Google (check proxy/network).
|
|
9
|
+
- **Add: global `proxyUrl` config (`DSH_AGY_PROXY_URL` env works too).** GUI hosts don't inherit shell proxy env — the #1 reason plugin runs time out while the same command in a terminal works. An account's dedicated `proxyUrl` wins, then the global config; the effective proxy is injected into every agy spawn (`ALL_PROXY`/`HTTP(S)_PROXY`) and used by the quota / OAuth fetches. `/agy pool` now shows the effective proxy per account.
|
|
10
|
+
- **Fix (macOS): recurring "create login keychain" password popup.** Pool accounts run with an isolated HOME, and macOS has **no login keychain inside it** (`security default-keychain` → "A default keychain could not be found"), so agy's keyring save triggered the `system.keychain.create.loginkc` password dialog on every ~hourly token refresh; denying it fell back to the on-disk token (auth kept working — exit status 45/154 in agy logs). The plugin now provisions a throwaway per-account keychain and sets it as the default under that HOME — idempotently at boot, before every spawn and before every login — so keyring writes are silent and truly per-account.
|
|
11
|
+
- **Fix: transient network errors no longer flag accounts as "needs re-login".** Both flagging paths were too eager: the run-failure path matched any message containing `/auth/i` (e.g. `oauth2.googleapis.com … i/o timeout` transport noise) and the token-refresh path flagged **any** `token endpoint 400` — including captive-portal / proxy error pages. Both now require an authoritative OAuth state (`invalid_grant` / `invalid_client` / `revoked` / `disabled` / `unauthorized_client`) and transport-shaped failures are exempt — even a genuine AUTH classification is not trusted when the message is transport-shaped. A false flag also **removed the account from pool rotation** (`selectAccount` skips authRequired), so this was silent capacity loss plus a scary badge that the next manual quota refresh would immediately disprove.
|
|
12
|
+
- **Add: flagged accounts self-heal in the background.** `markAuthRequired` now stamps `authMarkedAt`; after 10 minutes the background poll includes the flagged account for one token verification — if the stored refresh token still works the flag clears itself (previously this required a manual 刷新额度 click); if it is truly dead the flag is re-stamped. Risk-control exposure: at most one token-endpoint call per 10 minutes per flagged account.
|
|
13
|
+
- **Add: "In use" badge in the Antigravity console.** Each account card now shows a green zap badge when the sticky scheduler is currently routing runs to it (per model family, from the pool's `activeAccountIds`), answering "which account is agy using right now" at a glance. Localized (zh/en/pt-BR/es).
|
|
14
|
+
## 0.4.40 (2026-09-28)
|
|
15
|
+
|
|
16
|
+
### English
|
|
17
|
+
|
|
18
|
+
- **Fix (issue #34): post-tool hops dropped the task text and images.** dsh-llm 0.1.7 promotes tool results to `role: 'tool'` (0.1.x used `role: 'user'` + `source.callId`). `detectContinuation` and the trailing-span extractor only accepted the 0.1.x shape, so every hop after the first tool round-trip lost the mirror continuation, assembled an empty/digest-only prompt (`request carries no user text` or "what should I do next?"), and silently dropped image attachments. Both schemas are now accepted (`message.toolCallId`, `source.callId`, nested `tool-result` blocks); nested tool-result text and images are forwarded.
|
|
19
|
+
- **Fix (issue #35): continuation prompts must keep the live task.** `buildDigest` used to let a wall of tool-result text consume the 8K budget and truncate the user's question away. The latest real user turn is now reserved at the head of the digest; the assembled prompt always re-states it whenever the trailing span is tool-results only. A dead loop of "read my own agy logs" with no task can no longer start.
|
|
20
|
+
- **Fix (issue #35): aborted runs now persist the agy conversation id.** Binding was only written when `failure === null`, but a tool-cut / caller abort / timeout is exactly the path that used to drop it — so the next hop always started a brand-new agy conversation (`conversationID=""`) with digest-only prompt. The id is now harvested from a live run before steer-preemption and persisted on abort/timeout/process-exit (auth / rate-limit / conversation-rejected still drop the binding).
|
|
21
|
+
- **Fix (issue #32): region eligibility refusals get a clear cause.** agy exits 1 with `Eligibility check failed: … not currently available in your location` (e.g. Seoul). This is a Google-side account/region restriction, not a proxy or quota bug. The bridge now classifies it and surfaces an explicit `AGY_ERROR` with guidance instead of a bare `PROCESS_EXIT`.
|
|
22
|
+
|
|
23
|
+
### 中文 (Chinese)
|
|
24
|
+
|
|
25
|
+
- **修复:UI 的 Stop 按钮杀不掉离线卡死的 run。** `killTree` 只对进程组发一次 SIGTERM;而 agy 困在离线拨号重试里时会无视 SIGTERM(实测 `kill <pid>` 无效、只有 `kill -9` 能杀掉)。现在 runner 在 SIGTERM 3 秒后对整组升级 SIGKILL,Stop 按钮必定生效。
|
|
26
|
+
- **修复:连不上 Google 时 run 永远转圈。** 旧看门狗把 stderr 输出当作活动——agy 连不上 Google 时每 ~35 秒往 stderr 写一条拨号重试日志,于是一个连一条 stdout 事件都没有的 run 一直“活着”,直到 240 分钟的 print-timeout。看门狗现在**只认 stdout**;登录流程通过新的 `stderrKeepalive` 选项继续保活(浏览器登录本来就会长时间没有 stdout)。零 stdout 事件的超时现在会直接说明原因:无法连接 Google(检查代理/网络)。
|
|
27
|
+
- **新增:全局 `proxyUrl` 配置(也支持 `DSH_AGY_PROXY_URL` 环境变量)。** GUI 应用不继承 shell 的代理环境变量——这是“终端里没问题、插件里必超时”的头号原因。账号专属 `proxyUrl` 优先,其次全局配置;生效代理会注入每个 agy 进程(`ALL_PROXY`/`HTTP(S)_PROXY`),额度 / OAuth 请求同样走代理。`/agy pool` 现在会显示每个账号的生效代理。
|
|
28
|
+
- **修复(macOS):反复弹出“创建登录钥匙串”密码框。** 池账号使用隔离 HOME,而隔离 HOME 里**没有登录钥匙串**(`security default-keychain` → "A default keychain could not be found"),agy 每次约每小时的 token 续期写入都会触发 `system.keychain.create.loginkc` 密码弹窗;取消后回退磁盘 token(认证不受影响——agy 日志里的 exit status 45/154 即此)。插件现在会为每个池账号预置一个专属的一次性钥匙串并设为该 HOME 的默认——在启动时、每次拉起前、每次登录前幂等执行——钥匙串写入从此静默,且真正做到按账号隔离。
|
|
29
|
+
- **修复:瞬时网络错误不再把账号标记为「需重新登录」(误判修复)。** 两条标记路径都过于激进:run 失败路径会匹配任何包含 `/auth/i` 的报错(比如 `oauth2.googleapis.com … i/o timeout` 这种纯传输噪音);token 刷新路径会把**任何** `token endpoint 400` 都当成认证死亡——包括代理/门户返回的错误页。现在两条路径都要求出现权威的 OAuth 状态(`invalid_grant` / `invalid_client` / `revoked` / `disabled` / `unauthorized_client`),传输型失败一律豁免——即使带 AUTH 分类,只要报错文本是传输形态也不标记。误判的代价不只是吓人的徽标:被标记的账号会被调度器**排除出轮换**,等于静默损失一个账号,直到手动「刷新额度」把它证伪。
|
|
30
|
+
- **新增:被标记的账号后台自动复验。** `markAuthRequired` 现在会记录 `authMarkedAt`;10 分钟后后台轮询会把该账号放进来做一次 token 验证——刷新令牌若仍可用,标记自动清除(以前必须手动点「刷新额度」);若真的失效则重新盖章。风控代价:每个被标记账号至多每 10 分钟一次 token endpoint 调用。
|
|
31
|
+
- **新增:控制台「当前使用」标识。** 账号卡片现在会显示绿色 ⚡「当前使用」徽标(基于号池调度器的 `activeAccountIds`,按模型族标注),一眼看出 agy 当前正在用哪个账号。四种语言(zh/en/pt-BR/es)均已本地化。
|
|
32
|
+
## 0.4.40 (2026-09-28)
|
|
33
|
+
|
|
34
|
+
### 中文 (Chinese)
|
|
35
|
+
|
|
36
|
+
- **修复 #34:工具往返之后任务文本和图片丢失。** dsh-llm 0.1.7 把工具结果提升为 `role: 'tool'`(0.1.x 是 `role: 'user'` + `source.callId`)。`detectContinuation` 与尾部消息提取只认 0.1.x 形态,于是第一次工具往返之后的每一跳都丢掉镜像续跑,拼出空 prompt 或纯 digest(`request carries no user text`,或 agy 反问「接下来做什么」),图片附件也一并丢失。现在两种 schema 都识别(`message.toolCallId` / `source.callId` / 嵌套 `tool-result`),嵌套工具结果文本与图片会一并转发。
|
|
37
|
+
- **修复 #35:续跑提示词必须保住任务。** 旧 `buildDigest` 允许大段工具结果吃掉 8K 预算,把用户的问题截掉。现在最新一条真实用户消息固定保留在 digest 开头;只要尾部只有工具结果,组装出的 prompt 也会重申该任务。「反复读自己日志、没有题目」的死循环不会再出现。
|
|
38
|
+
- **修复 #35:aborted 路径也会落库 conversationId。** 旧逻辑只在 `failure === null` 时写 binding,而工具切断 / 调用方中止 / 超时恰恰是最容易丢 id 的路径——下一跳于是每次都开全新 agy 会话(`conversationID=""`)+ 纯 digest。现在会在 steer 抢占前从 live run 收割 id,并在 abort/timeout/process-exit 时落库(auth / 限流 / 会话失效仍会删除 binding)。
|
|
39
|
+
- **修复 #32:地区资格拒绝给出明确原因。** agy 以 `Eligibility check failed: … not currently available in your location` 退出(例如首尔)。这是 Google 账号/地区限制,不是代理或额度问题。桥接层现在会识别并给出明确的 `AGY_ERROR` 与指引,而不是裸的 `PROCESS_EXIT`。
|
|
40
|
+
|
|
41
|
+
## 0.4.39 (2026-09-21)
|
|
42
|
+
|
|
43
|
+
### English
|
|
44
|
+
|
|
45
|
+
- **Fix (issue #33): DSH session interrupt during long tools (maven / compile).** An ACTIVE agy tool is silent on stdout for many minutes while it builds. The idle watchdog used the normal `timeoutMs` (default 10 min) and killed the healthy run mid-compile. While any tool step is outstanding the idle budget now rises to the print-mode ceiling (≥ 4 h) and restores to `timeoutMs` when the last tool completes (`RunningProcess.noteActivity`).
|
|
46
|
+
- **Fix (issue #33): identical retries no longer preempt a live long tool.** Session ownership is decided after prompt assembly: same prompt + live run → `BUSY` (leave the maven/compile alone); different prompt + live run → steer-preempt; settled run → retry allowed (still honors issue #32).
|
|
47
|
+
|
|
48
|
+
### 中文 (Chinese)
|
|
49
|
+
|
|
50
|
+
- **修复 #33:耗时工具(Maven/编译)期间会话被中断。** 工具 ACTIVE 后 agy 在 stdout 上长时间静默是正常的;旧空闲看门狗按 `timeoutMs`(默认 10 分钟)把还在编译的进程杀掉。现在只要存在未完成的工具步骤,空闲预算就抬到 print-mode 上限(≥ 4 小时),工具全部结束后恢复 `timeoutMs`。
|
|
51
|
+
- **修复 #33:同 prompt 重试不再抢占正在跑的长任务。** 会话所有权在 prompt 组装之后判定:同 prompt + live run → `BUSY`(不杀 Maven/编译);不同 prompt + live run → 作为 steer 抢占;已 settle 的请求仍放行重试(兼容 #32)。
|
|
52
|
+
|
|
3
53
|
## 0.4.38 (2026-09-21)
|
|
4
54
|
|
|
5
55
|
### English
|
package/dist/client.js
CHANGED
|
@@ -48,6 +48,7 @@ window.__ModuleLoader__.load({
|
|
|
48
48
|
"status.authRequired": "需重新登录",
|
|
49
49
|
"status.primary": "主用",
|
|
50
50
|
"status.proxy": "代理",
|
|
51
|
+
"status.inUse": "当前使用",
|
|
51
52
|
"account.add": "添加 Google 账号",
|
|
52
53
|
"account.aliasDefault": "备用 Google 账号 {number}",
|
|
53
54
|
"account.defaultAlias": "主账号(系统登录)",
|
|
@@ -138,6 +139,7 @@ window.__ModuleLoader__.load({
|
|
|
138
139
|
"status.authRequired": "Sign in again required",
|
|
139
140
|
"status.primary": "Primary",
|
|
140
141
|
"status.proxy": "Proxy",
|
|
142
|
+
"status.inUse": "In use",
|
|
141
143
|
"account.add": "Add Google account",
|
|
142
144
|
"account.aliasDefault": "Backup Google account {number}",
|
|
143
145
|
"account.defaultAlias": "Primary account (system sign-in)",
|
|
@@ -228,6 +230,7 @@ window.__ModuleLoader__.load({
|
|
|
228
230
|
"status.authRequired": "É necessário entrar novamente",
|
|
229
231
|
"status.primary": "Principal",
|
|
230
232
|
"status.proxy": "Proxy",
|
|
233
|
+
"status.inUse": "Em uso",
|
|
231
234
|
"account.add": "Adicionar conta Google",
|
|
232
235
|
"account.aliasDefault": "Conta Google reserva {number}",
|
|
233
236
|
"account.defaultAlias": "Conta principal (login do sistema)",
|
|
@@ -318,6 +321,7 @@ window.__ModuleLoader__.load({
|
|
|
318
321
|
"status.authRequired": "Debe iniciar sesión de nuevo",
|
|
319
322
|
"status.primary": "Principal",
|
|
320
323
|
"status.proxy": "Proxy",
|
|
324
|
+
"status.inUse": "En uso",
|
|
321
325
|
"account.add": "Añadir cuenta de Google",
|
|
322
326
|
"account.aliasDefault": "Cuenta de Google de respaldo {number}",
|
|
323
327
|
"account.defaultAlias": "Cuenta principal (inicio de sesión del sistema)",
|
|
@@ -2725,8 +2729,10 @@ body.dark,
|
|
|
2725
2729
|
color: "var(--agy-text-primary)"
|
|
2726
2730
|
} }, brandIcon(FAMILY_BRAND[familyKey], 14), h("span", null, label)), renderLine(t("quota.fiveHour"), pct5h, c5h, w5h.resetText ? w5h.resetText + cdNote : cdNote.replace(/^ · /, "")), renderLine(t("quota.weekly"), pctWeekly, cWeekly, wWeekly.resetText));
|
|
2727
2731
|
};
|
|
2732
|
+
const inUseIds = new Set(Object.values(pool?.activeAccountIds ?? {}));
|
|
2728
2733
|
const renderedAccountCards = accounts.map((acc) => {
|
|
2729
2734
|
const isPrimary = acc.id === pool?.primaryAccountId;
|
|
2735
|
+
const isInUse = inUseIds.has(acc.id);
|
|
2730
2736
|
const hasCooldown = Object.entries(acc.cooldowns).some(([, cd]) => cd && cd.cooldownUntil > Date.now());
|
|
2731
2737
|
const isAuthRequired = acc.authRequired;
|
|
2732
2738
|
const dotColor = !acc.enabled ? "#64748b" : isAuthRequired ? "#ef4444" : hasCooldown ? "#f59e0b" : "#10b981";
|
|
@@ -2792,7 +2798,14 @@ body.dark,
|
|
|
2792
2798
|
} }, uiIcon("mail", 11, "var(--agy-badge-email-text)"), acc.email) : null, isPrimary ? h("span", { style: {
|
|
2793
2799
|
...S.badgePrimary,
|
|
2794
2800
|
gap: "4px"
|
|
2795
|
-
} }, uiIcon("star", 10, "var(--agy-badge-primary-text)"), t("status.primary")) : null,
|
|
2801
|
+
} }, uiIcon("star", 10, "var(--agy-badge-primary-text)"), t("status.primary")) : null, isInUse ? h("span", { style: {
|
|
2802
|
+
...S.badgeTag,
|
|
2803
|
+
background: "rgba(16, 185, 129, 0.12)",
|
|
2804
|
+
color: "#10b981",
|
|
2805
|
+
borderColor: "rgba(16, 185, 129, 0.45)",
|
|
2806
|
+
gap: "4px",
|
|
2807
|
+
fontWeight: 700
|
|
2808
|
+
} }, uiIcon("zap", 11, "#10b981"), t("status.inUse")) : null, acc.proxyUrl ? h("span", { style: {
|
|
2796
2809
|
...S.badgeTag,
|
|
2797
2810
|
background: "var(--agy-badge-proxy-bg)",
|
|
2798
2811
|
color: "var(--agy-badge-proxy-text)",
|
package/dist/index.js
CHANGED
|
@@ -102,9 +102,34 @@ function defaultConfig() {
|
|
|
102
102
|
autoFallbackModel: false,
|
|
103
103
|
logRetentionDays: 7,
|
|
104
104
|
disableTelemetry: true,
|
|
105
|
-
quotaPollIntervalMs: 9e5
|
|
105
|
+
quotaPollIntervalMs: 9e5,
|
|
106
|
+
proxyUrl: ""
|
|
106
107
|
};
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Authoritative OAuth/token states that genuinely prove a login is dead.
|
|
111
|
+
* A bare 400 (captive portal, proxy error page) or a body without an OAuth
|
|
112
|
+
* error code must NOT flag an account — that used to produce "needs
|
|
113
|
+
* re-login" false positives which a manual quota refresh immediately
|
|
114
|
+
* disproved.
|
|
115
|
+
*/
|
|
116
|
+
function isAuthoritativeAuthError(message) {
|
|
117
|
+
return /invalid_grant|invalid_client|revoked|disabled|unauthorized_client/i.test(message);
|
|
118
|
+
}
|
|
119
|
+
/** Transient transport failures — never proof that a login is dead. */
|
|
120
|
+
function looksLikeNetworkError(message) {
|
|
121
|
+
return /dial tcp|fetch failed|econn(?:refused|reset|aborted)|enotfound|ehostunreach|enetunreach|socket hang up|getaddrinfo|bad gateway|service unavailable/i.test(message);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Decide whether a failed call may flag an account as "needs re-login".
|
|
125
|
+
* Genuine AUTH classification wins (unless the failure was transport-shaped),
|
|
126
|
+
* and the message must name an authoritative token state otherwise.
|
|
127
|
+
*/
|
|
128
|
+
function shouldMarkAuthRequired(code, message) {
|
|
129
|
+
if (looksLikeNetworkError(message)) return false;
|
|
130
|
+
if (code === "AUTH") return true;
|
|
131
|
+
return isAuthoritativeAuthError(message);
|
|
132
|
+
}
|
|
108
133
|
const Err = {
|
|
109
134
|
AUTH: "AUTH",
|
|
110
135
|
AGY_NOT_INSTALLED: "AGY_NOT_INSTALLED",
|
|
@@ -121,6 +146,16 @@ const Err = {
|
|
|
121
146
|
function looksLikeAuthFailure(text) {
|
|
122
147
|
return /authentication required|authentication failed|please sign in|not signed in|timed out waiting for authentication/i.test(text);
|
|
123
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Google-side account/region eligibility refusals (issue #32). agy exits 1
|
|
151
|
+
* with "Eligibility check failed: … not currently available in your
|
|
152
|
+
* location" — not an auth bug and not something the bridge can retry past.
|
|
153
|
+
*/
|
|
154
|
+
function looksLikeEligibilityFailure(text) {
|
|
155
|
+
return /eligibility check failed|not eligible for antigravity|not currently available in your (?:location|country|region)|not available in your (?:location|country|region)/i.test(text);
|
|
156
|
+
}
|
|
157
|
+
/** One-line guidance for an eligibility refusal (shown in the turn error). */
|
|
158
|
+
const ELIGIBILITY_ERROR_HINT = "Google Antigravity is not eligible for this account/region. Use an account and network location where Antigravity is available (see https://antigravity.google).";
|
|
124
159
|
function extractAuthUrl(text) {
|
|
125
160
|
const m = text.match(/https:\/\/accounts\.google\.com\/\S+/);
|
|
126
161
|
if (!m) return void 0;
|
|
@@ -254,7 +289,8 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
254
289
|
autoFallbackModel: asBool(get("autoFallbackModel")) ?? base.autoFallbackModel,
|
|
255
290
|
logRetentionDays: asNum(get("logRetentionDays")) ?? base.logRetentionDays,
|
|
256
291
|
disableTelemetry: asBool(get("disableTelemetry")) ?? base.disableTelemetry,
|
|
257
|
-
quotaPollIntervalMs: asNum(get("quotaPollIntervalMs")) ?? base.quotaPollIntervalMs
|
|
292
|
+
quotaPollIntervalMs: asNum(get("quotaPollIntervalMs")) ?? base.quotaPollIntervalMs,
|
|
293
|
+
proxyUrl: asString(get("proxyUrl")) ?? base.proxyUrl
|
|
258
294
|
};
|
|
259
295
|
if (env.DSH_AGY_ENABLED !== void 0) cfg.enabled = asBool(env.DSH_AGY_ENABLED) ?? cfg.enabled;
|
|
260
296
|
if (env.DSH_AGY_BIN) cfg.agyBin = env.DSH_AGY_BIN;
|
|
@@ -304,6 +340,7 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
304
340
|
const q = asNum(env.DSH_AGY_QUOTA_POLL_INTERVAL_MS);
|
|
305
341
|
if (q && q >= 6e4) cfg.quotaPollIntervalMs = q;
|
|
306
342
|
}
|
|
343
|
+
if (env.DSH_AGY_PROXY_URL) cfg.proxyUrl = env.DSH_AGY_PROXY_URL;
|
|
307
344
|
return cfg;
|
|
308
345
|
}
|
|
309
346
|
//#endregion
|
|
@@ -331,6 +368,23 @@ function shouldPollAccount(account) {
|
|
|
331
368
|
for (const cd of Object.values(account.cooldowns)) if (cd && cd.cooldownUntil > now) return false;
|
|
332
369
|
return true;
|
|
333
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* After this long, an authRequired flag becomes eligible for ONE background
|
|
373
|
+
* re-verification per poll cycle: a single transient failure used to
|
|
374
|
+
* quarantine an account until a manual quota refresh cleared it.
|
|
375
|
+
*/
|
|
376
|
+
const AUTH_REVERIFY_DELAY_MS = 6e5;
|
|
377
|
+
/**
|
|
378
|
+
* A flagged account whose flag is old enough (or whose flag predates the
|
|
379
|
+
* authMarkedAt bookkeeping) may be re-verified in the background. If the
|
|
380
|
+
* stored token still works the flag clears itself; if not, the flag is
|
|
381
|
+
* simply re-stamped.
|
|
382
|
+
*/
|
|
383
|
+
function needsAuthReverify(account, now = Date.now()) {
|
|
384
|
+
if (!account.authRequired) return false;
|
|
385
|
+
if (typeof account.authMarkedAt !== "number") return true;
|
|
386
|
+
return now - account.authMarkedAt > AUTH_REVERIFY_DELAY_MS;
|
|
387
|
+
}
|
|
334
388
|
function defaultPoolData() {
|
|
335
389
|
return {
|
|
336
390
|
version: 1,
|
|
@@ -1978,6 +2032,64 @@ function isolatedHomeEnv(dir) {
|
|
|
1978
2032
|
}
|
|
1979
2033
|
return env;
|
|
1980
2034
|
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Standard proxy env block for one proxy URL. agy is a Go binary and only
|
|
2037
|
+
* honors HTTP(S)_PROXY / ALL_PROXY; GUI hosts don't inherit shell proxy
|
|
2038
|
+
* env, so the plugin injects this explicitly (global config or per-account).
|
|
2039
|
+
* Exported for tests.
|
|
2040
|
+
*/
|
|
2041
|
+
function proxyEnvFor(proxyUrl) {
|
|
2042
|
+
const url = proxyUrl?.trim();
|
|
2043
|
+
if (!url) return {};
|
|
2044
|
+
return {
|
|
2045
|
+
ALL_PROXY: url,
|
|
2046
|
+
HTTPS_PROXY: url,
|
|
2047
|
+
HTTP_PROXY: url,
|
|
2048
|
+
all_proxy: url,
|
|
2049
|
+
https_proxy: url,
|
|
2050
|
+
http_proxy: url
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* macOS: an isolated HOME has no login keychain, so the first keyring write
|
|
2055
|
+
* makes the Security framework pop the "create login keychain" password
|
|
2056
|
+
* dialog (right system.keychain.create.loginkc) on every token refresh.
|
|
2057
|
+
* Provisioning a throwaway per-account keychain and making it the default
|
|
2058
|
+
* under that HOME keeps keyring writes silent and fully per-account.
|
|
2059
|
+
* No-op on other platforms or when already provisioned. Never throws.
|
|
2060
|
+
* Exported for tests.
|
|
2061
|
+
*/
|
|
2062
|
+
function ensureIsolatedKeychain(dir, platform = process.platform, run = (file, args, env) => execFileSync(file, args, {
|
|
2063
|
+
stdio: "ignore",
|
|
2064
|
+
env,
|
|
2065
|
+
timeout: 1e4
|
|
2066
|
+
})) {
|
|
2067
|
+
if (platform !== "darwin") return false;
|
|
2068
|
+
if (dir === "") return false;
|
|
2069
|
+
const kc = join(dir, "Library", "Keychains", "login.keychain-db");
|
|
2070
|
+
if (existsSync(kc)) return false;
|
|
2071
|
+
try {
|
|
2072
|
+
mkdirSync(join(dir, "Library", "Keychains"), { recursive: true });
|
|
2073
|
+
const env = {
|
|
2074
|
+
...process.env,
|
|
2075
|
+
HOME: dir
|
|
2076
|
+
};
|
|
2077
|
+
run("security", [
|
|
2078
|
+
"create-keychain",
|
|
2079
|
+
"-p",
|
|
2080
|
+
randomBytes(18).toString("base64url"),
|
|
2081
|
+
kc
|
|
2082
|
+
], env);
|
|
2083
|
+
run("security", [
|
|
2084
|
+
"default-keychain",
|
|
2085
|
+
"-s",
|
|
2086
|
+
kc
|
|
2087
|
+
], env);
|
|
2088
|
+
return true;
|
|
2089
|
+
} catch {
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
1981
2093
|
function resolveAgyBin(cfg) {
|
|
1982
2094
|
const candidates = [];
|
|
1983
2095
|
if (cfg.agyBin !== "") candidates.push(cfg.agyBin);
|
|
@@ -2091,6 +2203,7 @@ function withAgyQuietEnv(env = process.env) {
|
|
|
2091
2203
|
AGY_CLI_INTERACTIVE_HEADLESS: env.AGY_CLI_INTERACTIVE_HEADLESS ?? "1"
|
|
2092
2204
|
};
|
|
2093
2205
|
}
|
|
2206
|
+
const escalating = /* @__PURE__ */ new WeakSet();
|
|
2094
2207
|
function killTree(child) {
|
|
2095
2208
|
if (child.pid === void 0) return;
|
|
2096
2209
|
if (IS_WIN) {
|
|
@@ -2117,7 +2230,24 @@ function killTree(child) {
|
|
|
2117
2230
|
try {
|
|
2118
2231
|
child.kill("SIGTERM");
|
|
2119
2232
|
} catch {}
|
|
2233
|
+
return;
|
|
2120
2234
|
}
|
|
2235
|
+
if (escalating.has(child)) return;
|
|
2236
|
+
escalating.add(child);
|
|
2237
|
+
const pid = child.pid;
|
|
2238
|
+
const escalate = setTimeout(() => {
|
|
2239
|
+
try {
|
|
2240
|
+
process.kill(-pid, "SIGKILL");
|
|
2241
|
+
} catch {}
|
|
2242
|
+
try {
|
|
2243
|
+
child.kill("SIGKILL");
|
|
2244
|
+
} catch {}
|
|
2245
|
+
}, 3e3);
|
|
2246
|
+
escalate.unref?.();
|
|
2247
|
+
child.once("exit", () => {
|
|
2248
|
+
clearTimeout(escalate);
|
|
2249
|
+
escalating.delete(child);
|
|
2250
|
+
});
|
|
2121
2251
|
}
|
|
2122
2252
|
function startAgyProcess(opts) {
|
|
2123
2253
|
const started = Date.now();
|
|
@@ -2162,13 +2292,14 @@ function startAgyProcess(opts) {
|
|
|
2162
2292
|
child.stdin?.end();
|
|
2163
2293
|
} catch {}
|
|
2164
2294
|
let watchdog = null;
|
|
2295
|
+
let idleBudgetMs = opts.timeoutMs;
|
|
2165
2296
|
const refreshWatchdog = () => {
|
|
2166
|
-
if (!
|
|
2297
|
+
if (!idleBudgetMs || idleBudgetMs <= 0 || settled) return;
|
|
2167
2298
|
if (watchdog) clearTimeout(watchdog);
|
|
2168
2299
|
watchdog = setTimeout(() => {
|
|
2169
2300
|
timedOut = true;
|
|
2170
2301
|
killTree(child);
|
|
2171
|
-
},
|
|
2302
|
+
}, idleBudgetMs);
|
|
2172
2303
|
};
|
|
2173
2304
|
refreshWatchdog();
|
|
2174
2305
|
const onAbort = () => {
|
|
@@ -2192,7 +2323,7 @@ function startAgyProcess(opts) {
|
|
|
2192
2323
|
}
|
|
2193
2324
|
});
|
|
2194
2325
|
child.stderr?.on("data", (chunk) => {
|
|
2195
|
-
refreshWatchdog();
|
|
2326
|
+
if (opts.stderrKeepalive) refreshWatchdog();
|
|
2196
2327
|
stderr = (stderr + chunk).slice(-4096);
|
|
2197
2328
|
});
|
|
2198
2329
|
return {
|
|
@@ -2223,6 +2354,10 @@ function startAgyProcess(opts) {
|
|
|
2223
2354
|
finish(null, null);
|
|
2224
2355
|
});
|
|
2225
2356
|
}),
|
|
2357
|
+
noteActivity: (budgetMs) => {
|
|
2358
|
+
if (budgetMs !== void 0) idleBudgetMs = budgetMs;
|
|
2359
|
+
refreshWatchdog();
|
|
2360
|
+
},
|
|
2226
2361
|
kill: (reason) => {
|
|
2227
2362
|
if (reason === "timeout") timedOut = true;
|
|
2228
2363
|
else aborted = true;
|
|
@@ -2668,9 +2803,48 @@ async function readStepThoughts(conversationId, stepIndex, accountHome) {
|
|
|
2668
2803
|
}
|
|
2669
2804
|
//#endregion
|
|
2670
2805
|
//#region src/host/adapter.ts
|
|
2806
|
+
/**
|
|
2807
|
+
* True for a tool-result message across dsh-llm schema revisions.
|
|
2808
|
+
*
|
|
2809
|
+
* 0.1.x: `role: 'user'` + `source: { kind: 'tool', callId }` and a sole
|
|
2810
|
+
* `tool-result` content block. 0.1.7+ promotes tool results to
|
|
2811
|
+
* `role: 'tool'` with `toolCallId` on the message itself. A call that only
|
|
2812
|
+
* looks at `role === 'user'` drops the whole trailing span after the first
|
|
2813
|
+
* tool round-trip (issues #34 / #35).
|
|
2814
|
+
*/
|
|
2815
|
+
function isToolResultMessage(m) {
|
|
2816
|
+
if (m.role === "tool") return true;
|
|
2817
|
+
if (m.source?.kind === "tool") return true;
|
|
2818
|
+
const content = m.content;
|
|
2819
|
+
return Array.isArray(content) && content.length > 0 && content.every((b) => b?.type === "tool-result");
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Correlation id of a tool-result message. Accepts `message.toolCallId`
|
|
2823
|
+
* (0.1.7+), `source.callId` (0.1.x), and nested `tool-result` blocks.
|
|
2824
|
+
*/
|
|
2825
|
+
function toolCallIdOf(m) {
|
|
2826
|
+
const direct = m.toolCallId;
|
|
2827
|
+
if (typeof direct === "string" && direct !== "") return direct;
|
|
2828
|
+
const src = m.source;
|
|
2829
|
+
if (typeof src?.callId === "string" && src.callId !== "") return src.callId;
|
|
2830
|
+
for (const raw of m.content ?? []) {
|
|
2831
|
+
const b = raw;
|
|
2832
|
+
if (b?.type === "tool-result" && typeof b.toolCallId === "string" && b.toolCallId !== "") return b.toolCallId;
|
|
2833
|
+
}
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2836
|
+
/** Flatten visible text, including nested tool-result payloads. */
|
|
2671
2837
|
function textOf(m) {
|
|
2672
2838
|
const parts = [];
|
|
2673
|
-
|
|
2839
|
+
const walk = (blocks) => {
|
|
2840
|
+
for (const raw of blocks ?? []) {
|
|
2841
|
+
const b = raw;
|
|
2842
|
+
if (b === void 0) continue;
|
|
2843
|
+
if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
2844
|
+
else if (b.type === "tool-result" && Array.isArray(b.content)) walk(b.content);
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
walk(m.content);
|
|
2674
2848
|
return parts.filter((s) => s !== "").join("\n");
|
|
2675
2849
|
}
|
|
2676
2850
|
function isForeignAssistant(m) {
|
|
@@ -2678,25 +2852,82 @@ function isForeignAssistant(m) {
|
|
|
2678
2852
|
const src = m.source;
|
|
2679
2853
|
return !src || src.provider !== "antigravity";
|
|
2680
2854
|
}
|
|
2681
|
-
/**
|
|
2855
|
+
/**
|
|
2856
|
+
* Latest real human turn (not a tool result, not assistant). This is the
|
|
2857
|
+
* live task; a digest or tool-result-only trailing span must never drop it
|
|
2858
|
+
* (issue #35: "续跑提示词里没有任务").
|
|
2859
|
+
*/
|
|
2860
|
+
function latestUserTaskText(messages) {
|
|
2861
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2862
|
+
const m = messages[i];
|
|
2863
|
+
if (m === void 0 || m.role === "system" || m.role === "assistant") continue;
|
|
2864
|
+
if (isToolResultMessage(m)) continue;
|
|
2865
|
+
const text = textOf(m);
|
|
2866
|
+
if (text !== "") return text;
|
|
2867
|
+
}
|
|
2868
|
+
return "";
|
|
2869
|
+
}
|
|
2870
|
+
/** Collect image refs from a message, walking nested tool-result payloads. */
|
|
2871
|
+
function collectImageRefs(m, out) {
|
|
2872
|
+
const walk = (blocks) => {
|
|
2873
|
+
for (const raw of blocks ?? []) {
|
|
2874
|
+
const b = raw;
|
|
2875
|
+
if (b === void 0) continue;
|
|
2876
|
+
if (b.type === "image") {
|
|
2877
|
+
if (b.attachment && typeof b.attachment === "object") out.push(b.attachment);
|
|
2878
|
+
else if (typeof b.attachmentId === "string") out.push(b);
|
|
2879
|
+
} else if (b.type === "tool-result" && Array.isArray(b.content)) walk(b.content);
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
walk(m.content);
|
|
2883
|
+
}
|
|
2884
|
+
function digestLabel(m) {
|
|
2885
|
+
if (isToolResultMessage(m)) return "Tool result: ";
|
|
2886
|
+
return m.role === "user" ? "User: " : "Assistant: ";
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Rolling digest of turns this agy conversation has not seen (ADR-7).
|
|
2890
|
+
*
|
|
2891
|
+
* Budget policy (issue #35): the latest real user task is reserved first so
|
|
2892
|
+
* a wall of tool-result text can never truncate the question away. Remaining
|
|
2893
|
+
* budget is filled from the newest other turns.
|
|
2894
|
+
*/
|
|
2682
2895
|
function buildDigest(messages, fromIdx, maxChars) {
|
|
2683
|
-
const
|
|
2684
|
-
|
|
2685
|
-
|
|
2896
|
+
const taskText = latestUserTaskText(messages.slice(fromIdx));
|
|
2897
|
+
const taskLine = taskText !== "" ? "User: " + taskText : "";
|
|
2898
|
+
const otherLines = [];
|
|
2899
|
+
for (let i = fromIdx; i < messages.length; i++) {
|
|
2686
2900
|
const m = messages[i];
|
|
2687
2901
|
if (m === void 0 || m.role === "system") continue;
|
|
2688
2902
|
const text = textOf(m);
|
|
2689
2903
|
if (text === "") continue;
|
|
2690
|
-
|
|
2904
|
+
if (taskLine !== "" && !isToolResultMessage(m) && m.role === "user" && text === taskText) continue;
|
|
2905
|
+
otherLines.push(digestLabel(m) + text);
|
|
2906
|
+
}
|
|
2907
|
+
let budget = maxChars;
|
|
2908
|
+
const kept = [];
|
|
2909
|
+
if (taskLine !== "") {
|
|
2910
|
+
if (taskLine.length > budget) {
|
|
2911
|
+
kept.push(taskLine.slice(0, Math.max(0, budget)));
|
|
2912
|
+
budget = 0;
|
|
2913
|
+
} else {
|
|
2914
|
+
kept.push(taskLine);
|
|
2915
|
+
budget -= taskLine.length;
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
const keptOther = [];
|
|
2919
|
+
for (let i = otherLines.length - 1; i >= 0 && budget > 0; i--) {
|
|
2920
|
+
const line = otherLines[i];
|
|
2691
2921
|
if (line.length > budget) {
|
|
2692
|
-
|
|
2922
|
+
keptOther.unshift(line.slice(0, Math.max(0, budget)));
|
|
2693
2923
|
break;
|
|
2694
2924
|
}
|
|
2925
|
+
keptOther.unshift(line);
|
|
2695
2926
|
budget -= line.length;
|
|
2696
|
-
parts.unshift(line);
|
|
2697
2927
|
}
|
|
2698
|
-
|
|
2699
|
-
|
|
2928
|
+
kept.push(...keptOther);
|
|
2929
|
+
if (kept.length === 0) return "";
|
|
2930
|
+
return "[conversation so far]\n" + kept.join("\n\n") + "\n[end of conversation so far]\n\n";
|
|
2700
2931
|
}
|
|
2701
2932
|
var ChunkQueue = class {
|
|
2702
2933
|
chunks = [];
|
|
@@ -2937,14 +3168,6 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
2937
3168
|
yield* this.driveSpan(rec, continuation.eventIndex + 1, true, isCodeMode);
|
|
2938
3169
|
return;
|
|
2939
3170
|
}
|
|
2940
|
-
let sessionPreempted = false;
|
|
2941
|
-
if (!isAux && sessionKey !== "") {
|
|
2942
|
-
const prev = this.activeRuns.get(sessionKey);
|
|
2943
|
-
if (prev !== void 0 && !prev.isSettled) {
|
|
2944
|
-
prev.requestAbort?.();
|
|
2945
|
-
sessionPreempted = true;
|
|
2946
|
-
}
|
|
2947
|
-
}
|
|
2948
3171
|
const catalog = this.deps.catalog.get();
|
|
2949
3172
|
const rawModel = options.model;
|
|
2950
3173
|
const model = resolveModelSlug(rawModel);
|
|
@@ -2969,7 +3192,10 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
2969
3192
|
break;
|
|
2970
3193
|
}
|
|
2971
3194
|
}
|
|
2972
|
-
const trailingUser = messages.slice(lastAssistantIdx + 1).filter((m) =>
|
|
3195
|
+
const trailingUser = messages.slice(lastAssistantIdx + 1).filter((m) => {
|
|
3196
|
+
const role = m.role;
|
|
3197
|
+
return role === "user" || role === "tool";
|
|
3198
|
+
});
|
|
2973
3199
|
if (!isAux && cfg.rateLimitPerMinute > 0) {
|
|
2974
3200
|
const now = Date.now();
|
|
2975
3201
|
const windowStart = now - 6e4;
|
|
@@ -3002,6 +3228,16 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3002
3228
|
}
|
|
3003
3229
|
const sessionAccountKey = account ? `${sessionKey}:${account.id}` : sessionKey;
|
|
3004
3230
|
let binding = sessionAccountKey !== "" ? this.deps.store.get(sessionAccountKey) : void 0;
|
|
3231
|
+
if (binding === void 0 && !isAux && sessionKey !== "") {
|
|
3232
|
+
const live = this.activeRuns.get(sessionKey);
|
|
3233
|
+
const liveCid = live !== void 0 && !live.isSettled ? live.conversationId : null;
|
|
3234
|
+
if (liveCid) binding = {
|
|
3235
|
+
conversationId: liveCid,
|
|
3236
|
+
lastMessageCount: Math.max(0, lastAssistantIdx + 1),
|
|
3237
|
+
updatedAt: Date.now(),
|
|
3238
|
+
model: activeModel === "" ? cfg.defaultModel : activeModel
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3005
3241
|
const currentModel = activeModel === "" ? cfg.defaultModel : activeModel;
|
|
3006
3242
|
if (!isAux && binding !== void 0 && binding.model && binding.model !== currentModel) {
|
|
3007
3243
|
if (sessionAccountKey !== "") this.deps.store.delete(sessionAccountKey);
|
|
@@ -3026,7 +3262,11 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3026
3262
|
}
|
|
3027
3263
|
prompt = "[summarize this conversation for context compaction]\n\n" + parts.join("\n\n") + "\n\nProduce a compact summary that preserves decisions, file paths, and open tasks.";
|
|
3028
3264
|
} else {
|
|
3029
|
-
|
|
3265
|
+
const trailingText = trailingUser.map(textOf).filter((s) => s !== "");
|
|
3266
|
+
const taskText = latestUserTaskText(messages);
|
|
3267
|
+
const trailingJoined = trailingText.join("\n\n");
|
|
3268
|
+
if (taskText !== "") prompt = trailingText.includes(taskText) || trailingJoined.includes(taskText) ? trailingJoined : trailingJoined === "" ? taskText : taskText + "\n\n" + trailingJoined;
|
|
3269
|
+
else prompt = trailingJoined;
|
|
3030
3270
|
if (binding === void 0 && lastAssistantIdx >= 0) prompt = buildDigest(messages, 0, cfg.digestMaxChars) + prompt;
|
|
3031
3271
|
else if (binding !== void 0) {
|
|
3032
3272
|
const from = Math.min(binding.lastMessageCount, messages.length);
|
|
@@ -3039,12 +3279,10 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3039
3279
|
let stagedDirs = [];
|
|
3040
3280
|
if (!isAux) {
|
|
3041
3281
|
const imageRefs = [];
|
|
3042
|
-
for (const m of trailingUser)
|
|
3043
|
-
|
|
3044
|
-
if (
|
|
3045
|
-
|
|
3046
|
-
else if (typeof blk.attachmentId === "string") imageRefs.push(blk);
|
|
3047
|
-
}
|
|
3282
|
+
for (const m of trailingUser) collectImageRefs(m, imageRefs);
|
|
3283
|
+
if (imageRefs.length === 0) for (const m of messages) {
|
|
3284
|
+
if (m.role !== "user" || isToolResultMessage(m)) continue;
|
|
3285
|
+
collectImageRefs(m, imageRefs);
|
|
3048
3286
|
}
|
|
3049
3287
|
if (imageRefs.length > 0 && this.deps.readImage) {
|
|
3050
3288
|
const dir = cfg.mediaDir !== "" ? cfg.mediaDir : defaultMediaDir(stateDir());
|
|
@@ -3069,9 +3307,18 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3069
3307
|
const prevPrompt = this.activeSessionPrompts.get(sessionKey);
|
|
3070
3308
|
const liveRun = this.activeRuns.get(sessionKey);
|
|
3071
3309
|
const runLive = liveRun !== void 0 && !liveRun.isSettled;
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3310
|
+
const samePrompt = prevPrompt !== void 0 && prevPrompt.prompt === prompt;
|
|
3311
|
+
if (runLive && samePrompt) throw new LlmError("Duplicate request ignored: an identical request is already running for this session.", Err.BUSY);
|
|
3312
|
+
if (samePrompt && prevPrompt !== void 0 && prevPrompt.inFlight && liveRun === void 0 && now - prevPrompt.startedAt < 2e3) throw new LlmError("Duplicate request ignored: an identical request is already running for this session.", Err.BUSY);
|
|
3313
|
+
if (runLive && !samePrompt) {
|
|
3314
|
+
const liveCid = liveRun.conversationId;
|
|
3315
|
+
if (liveCid !== null && binding === void 0 && !isAux && sessionAccountKey !== "") binding = {
|
|
3316
|
+
conversationId: liveCid,
|
|
3317
|
+
lastMessageCount: Math.max(0, lastAssistantIdx + 1),
|
|
3318
|
+
updatedAt: Date.now(),
|
|
3319
|
+
model: currentModel
|
|
3320
|
+
};
|
|
3321
|
+
liveRun.requestAbort?.();
|
|
3075
3322
|
}
|
|
3076
3323
|
this.activeSessionPrompts.set(sessionKey, {
|
|
3077
3324
|
prompt,
|
|
@@ -3085,6 +3332,13 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3085
3332
|
const parser = new StreamJsonParser();
|
|
3086
3333
|
this.deps.onParser?.(parser);
|
|
3087
3334
|
let streamCid = null;
|
|
3335
|
+
const openToolKeys = /* @__PURE__ */ new Set();
|
|
3336
|
+
const printTimeoutMinutes = Math.max(240, Math.ceil(cfg.timeoutMs / 6e4));
|
|
3337
|
+
const longToolIdleMs = printTimeoutMinutes * 6e4;
|
|
3338
|
+
let runningProc = null;
|
|
3339
|
+
const applyToolIdleBudget = () => {
|
|
3340
|
+
runningProc?.noteActivity(openToolKeys.size > 0 ? longToolIdleMs : cfg.timeoutMs);
|
|
3341
|
+
};
|
|
3088
3342
|
const activeModelForArgs = activeModel === "" ? cfg.defaultModel : activeModel;
|
|
3089
3343
|
const effectiveAddDirs = workspaceRoot !== "" ? stagedDirs.includes(workspaceRoot) ? stagedDirs : [workspaceRoot, ...stagedDirs] : stagedDirs;
|
|
3090
3344
|
const argsBase = this.buildArgs({
|
|
@@ -3094,7 +3348,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3094
3348
|
conversationId: !isAux && binding !== void 0 ? binding.conversationId : void 0,
|
|
3095
3349
|
permissionMode: isAux ? "plan" : cfg.permissionMode,
|
|
3096
3350
|
timeoutMs: cfg.timeoutMs,
|
|
3097
|
-
printTimeoutMinutes
|
|
3351
|
+
printTimeoutMinutes,
|
|
3098
3352
|
extraArgs: cfg.extraArgs,
|
|
3099
3353
|
addDirs: effectiveAddDirs
|
|
3100
3354
|
});
|
|
@@ -3106,7 +3360,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3106
3360
|
conversationId: !isAux && binding !== void 0 ? binding.conversationId : void 0,
|
|
3107
3361
|
permissionMode: isAux ? "plan" : cfg.permissionMode,
|
|
3108
3362
|
timeoutMs: cfg.timeoutMs,
|
|
3109
|
-
printTimeoutMinutes
|
|
3363
|
+
printTimeoutMinutes,
|
|
3110
3364
|
extraArgs: cfg.extraArgs,
|
|
3111
3365
|
addDirs: effectiveAddDirs,
|
|
3112
3366
|
promptViaStdin: true
|
|
@@ -3119,6 +3373,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3119
3373
|
released = true;
|
|
3120
3374
|
release();
|
|
3121
3375
|
};
|
|
3376
|
+
if (account && account.dir) ensureIsolatedKeychain(account.dir);
|
|
3122
3377
|
const env = {
|
|
3123
3378
|
...process.env,
|
|
3124
3379
|
...cfg.disableTelemetry ? {
|
|
@@ -3128,14 +3383,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3128
3383
|
ANTIGRAVITY_DISABLE_TELEMETRY: "1"
|
|
3129
3384
|
} : {},
|
|
3130
3385
|
...account && account.dir ? isolatedHomeEnv(account.dir) : {},
|
|
3131
|
-
...account?.proxyUrl
|
|
3132
|
-
ALL_PROXY: account.proxyUrl,
|
|
3133
|
-
HTTPS_PROXY: account.proxyUrl,
|
|
3134
|
-
HTTP_PROXY: account.proxyUrl,
|
|
3135
|
-
all_proxy: account.proxyUrl,
|
|
3136
|
-
https_proxy: account.proxyUrl,
|
|
3137
|
-
http_proxy: account.proxyUrl
|
|
3138
|
-
} : {}
|
|
3386
|
+
...proxyEnvFor(account?.proxyUrl || cfg.proxyUrl || void 0)
|
|
3139
3387
|
};
|
|
3140
3388
|
if (account) {
|
|
3141
3389
|
const lastSpawn = this.lastAccountSpawnTime.get(account.id) ?? 0;
|
|
@@ -3159,10 +3407,20 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3159
3407
|
for (const ev of parser.feed(line + "\n")) {
|
|
3160
3408
|
if (ev.kind === "init" && ev.conversationId) streamCid = ev.conversationId;
|
|
3161
3409
|
if (ev.kind === "result" && ev.conversationId !== "") streamCid = ev.conversationId;
|
|
3410
|
+
if (ev.kind === "step" && ev.stepKind === "tool" && ev.tool) {
|
|
3411
|
+
if (!(ev.state === "DONE" || ev.state === "ERROR" || ev.state === void 0 && (ev.tool.output !== void 0 || ev.tool.error !== void 0))) {
|
|
3412
|
+
if (!openToolKeys.has(ev.stepKey)) {
|
|
3413
|
+
openToolKeys.add(ev.stepKey);
|
|
3414
|
+
applyToolIdleBudget();
|
|
3415
|
+
}
|
|
3416
|
+
} else if (openToolKeys.delete(ev.stepKey)) applyToolIdleBudget();
|
|
3417
|
+
}
|
|
3162
3418
|
rec.append(ev);
|
|
3163
3419
|
}
|
|
3164
3420
|
}
|
|
3165
3421
|
});
|
|
3422
|
+
runningProc = proc;
|
|
3423
|
+
applyToolIdleBudget();
|
|
3166
3424
|
if (!isAux && sessionKey !== "") {
|
|
3167
3425
|
rec.requestAbort = () => proc.kill("abort");
|
|
3168
3426
|
this.activeRuns.set(sessionKey, rec);
|
|
@@ -3193,12 +3451,14 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3193
3451
|
code: "ABORTED",
|
|
3194
3452
|
message: "agy run aborted by caller"
|
|
3195
3453
|
};
|
|
3196
|
-
else if (outcome.timedOut)
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3454
|
+
else if (outcome.timedOut) {
|
|
3455
|
+
const neverSpoke = outcome.stdout.trim() === "";
|
|
3456
|
+
failure = {
|
|
3457
|
+
kind: "error",
|
|
3458
|
+
code: Err.TIMEOUT,
|
|
3459
|
+
message: "agy run was idle for " + cfg.timeoutMs + "ms without output" + (neverSpoke ? " — agy never emitted a single event; it is likely unable to reach Google (check proxy/network). 无法连接 Google,请检查代理或网络配置" : "")
|
|
3460
|
+
};
|
|
3461
|
+
} else if (sawAuthFailure(parser, outcome)) failure = {
|
|
3202
3462
|
kind: "error",
|
|
3203
3463
|
code: Err.AUTH,
|
|
3204
3464
|
message: "agy is not signed in — run /agy auth (or run agy once in a terminal) to login"
|
|
@@ -3210,6 +3470,13 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3210
3470
|
code: Err.AGY_ERROR,
|
|
3211
3471
|
message: "Google Antigravity quota / rate limit reached: " + bestMsg
|
|
3212
3472
|
};
|
|
3473
|
+
} else if (looksLikeEligibilityFailure(rawErrText) || looksLikeEligibilityFailure(parser.stats.lastResultError ?? "")) {
|
|
3474
|
+
const detail = parser.stats.lastResultError ?? (outcome.stderrTail !== "" ? brief(outcome.stderrTail) : "");
|
|
3475
|
+
failure = {
|
|
3476
|
+
kind: "error",
|
|
3477
|
+
code: Err.AGY_ERROR,
|
|
3478
|
+
message: ELIGIBILITY_ERROR_HINT + (detail !== "" ? " (" + detail + ")" : "")
|
|
3479
|
+
};
|
|
3213
3480
|
} else if (!consumable) {
|
|
3214
3481
|
if (outcome.code !== 0) {
|
|
3215
3482
|
const detail = parser.stats.lastResultError ?? (outcome.stderrTail !== "" ? brief(outcome.stderrTail) : "");
|
|
@@ -3244,9 +3511,17 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3244
3511
|
} else {
|
|
3245
3512
|
const effectiveRateLimit = isRateLimit || looksLikeRateLimit(failure.message);
|
|
3246
3513
|
if (account && looksLikeHardRateLimit(rawErrText)) this.deps.pool?.recordFailure(account.id, family, failure.message);
|
|
3247
|
-
if (account && (failure.code
|
|
3248
|
-
|
|
3249
|
-
|
|
3514
|
+
if (account && shouldMarkAuthRequired(failure.code, failure.message)) this.deps.pool?.markAuthRequired(account.id, failure.message);
|
|
3515
|
+
const staleConversation = failure.code === Err.AUTH || effectiveRateLimit || failure.message && /conversation.*(not found|invalid|not recognized|expired|does not exist)|session.*(expired|invalid)/i.test(failure.message);
|
|
3516
|
+
if (!isAux && sessionAccountKey !== "" && staleConversation) this.deps.store.delete(sessionAccountKey);
|
|
3517
|
+
else if (!isAux && sessionAccountKey !== "" && !staleConversation) {
|
|
3518
|
+
const finalId = binding !== void 0 ? binding.conversationId : conversationId;
|
|
3519
|
+
if (finalId) this.deps.store.set(sessionAccountKey, {
|
|
3520
|
+
conversationId: finalId,
|
|
3521
|
+
lastMessageCount: messages.length,
|
|
3522
|
+
updatedAt: Date.now(),
|
|
3523
|
+
model: activeModel
|
|
3524
|
+
});
|
|
3250
3525
|
}
|
|
3251
3526
|
}
|
|
3252
3527
|
this.deps.onRun?.({
|
|
@@ -3364,6 +3639,11 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
3364
3639
|
* Detect a continuation span: the request's LAST message is the tool result
|
|
3365
3640
|
* of one of our mirrored agy tool calls. Its callId encodes the recording
|
|
3366
3641
|
* run and the event index to resume after.
|
|
3642
|
+
*
|
|
3643
|
+
* Accepts both dsh-llm tool-result shapes: `role: 'user'` + `source.callId`
|
|
3644
|
+
* (0.1.x) and `role: 'tool'` + `message.toolCallId` (0.1.7+). Rejecting the
|
|
3645
|
+
* latter made every post-tool hop spawn a fresh agy process with a
|
|
3646
|
+
* digest-only prompt (issues #34 / #35).
|
|
3367
3647
|
*/
|
|
3368
3648
|
function detectContinuation(messages) {
|
|
3369
3649
|
let i = messages.length - 1;
|
|
@@ -3373,10 +3653,13 @@ function detectContinuation(messages) {
|
|
|
3373
3653
|
i--;
|
|
3374
3654
|
}
|
|
3375
3655
|
const last = messages[i];
|
|
3376
|
-
if (last === void 0
|
|
3377
|
-
const
|
|
3378
|
-
if (
|
|
3379
|
-
|
|
3656
|
+
if (last === void 0) return null;
|
|
3657
|
+
const role = last.role;
|
|
3658
|
+
if (role !== "user" && role !== "tool") return null;
|
|
3659
|
+
if (!isToolResultMessage(last)) return null;
|
|
3660
|
+
const callId = toolCallIdOf(last);
|
|
3661
|
+
if (callId === null) return null;
|
|
3662
|
+
return parseMirrorCallId(callId);
|
|
3380
3663
|
}
|
|
3381
3664
|
//#endregion
|
|
3382
3665
|
//#region src/host/oneshot.ts
|
|
@@ -27450,6 +27733,7 @@ var AuthHelper = class {
|
|
|
27450
27733
|
if (!bin) return null;
|
|
27451
27734
|
this.cancel();
|
|
27452
27735
|
this.capturedUrl = null;
|
|
27736
|
+
if (homeDir) ensureIsolatedKeychain(homeDir);
|
|
27453
27737
|
const proc = startAgyProcess({
|
|
27454
27738
|
bin,
|
|
27455
27739
|
args: [
|
|
@@ -27462,17 +27746,11 @@ var AuthHelper = class {
|
|
|
27462
27746
|
],
|
|
27463
27747
|
timeoutMs: 3e5,
|
|
27464
27748
|
keepStdin: true,
|
|
27749
|
+
stderrKeepalive: true,
|
|
27465
27750
|
env: {
|
|
27466
27751
|
...process.env,
|
|
27467
27752
|
...homeDir ? isolatedHomeEnv(homeDir) : {},
|
|
27468
|
-
...proxyUrl
|
|
27469
|
-
ALL_PROXY: proxyUrl,
|
|
27470
|
-
HTTPS_PROXY: proxyUrl,
|
|
27471
|
-
HTTP_PROXY: proxyUrl,
|
|
27472
|
-
all_proxy: proxyUrl,
|
|
27473
|
-
https_proxy: proxyUrl,
|
|
27474
|
-
http_proxy: proxyUrl
|
|
27475
|
-
} : {}
|
|
27753
|
+
...proxyEnvFor(proxyUrl)
|
|
27476
27754
|
},
|
|
27477
27755
|
onLine: (line) => {
|
|
27478
27756
|
if (this.capturedUrl) return;
|
|
@@ -27693,7 +27971,7 @@ async function handle(deps, raw) {
|
|
|
27693
27971
|
const sec = Math.ceil((cd.cooldownUntil - Date.now()) / 1e3);
|
|
27694
27972
|
cds.push(`${fam} cooldown: ${sec}s left (${cd.reason})`);
|
|
27695
27973
|
}
|
|
27696
|
-
lines.push(`${i + 1}. **${a.alias}** (${a.email || a.id})${isPrimary ? " ⭐ primary" : ""}`, ` - Proxy: ${a.proxyUrl || "system default"}`, ` - Quota: Gemini ${qG} | Claude ${qC} | GPT-OSS ${qO}`, cds.length > 0 ? ` - ⚠️ ${cds.join("; ")}` : " - Status: 🟢 Ready");
|
|
27974
|
+
lines.push(`${i + 1}. **${a.alias}** (${a.email || a.id})${isPrimary ? " ⭐ primary" : ""}`, ` - Proxy: ${a.proxyUrl || deps.cfg().proxyUrl || "system default"}`, ` - Quota: Gemini ${qG} | Claude ${qC} | GPT-OSS ${qO}`, cds.length > 0 ? ` - ⚠️ ${cds.join("; ")}` : " - Status: 🟢 Ready");
|
|
27697
27975
|
}
|
|
27698
27976
|
return ok(lines.join("\n"));
|
|
27699
27977
|
}
|
|
@@ -28187,6 +28465,7 @@ var AccountPoolManager = class {
|
|
|
28187
28465
|
const acc = this.getAccount(id);
|
|
28188
28466
|
if (!acc) return;
|
|
28189
28467
|
acc.authRequired = true;
|
|
28468
|
+
acc.authMarkedAt = Date.now();
|
|
28190
28469
|
acc.authError = reason || "Authentication expired or revoked (invalid_grant)";
|
|
28191
28470
|
if (this.data.activeAccountIds) {
|
|
28192
28471
|
for (const [fam, accId] of Object.entries(this.data.activeAccountIds)) if (accId === id) delete this.data.activeAccountIds[fam];
|
|
@@ -28207,6 +28486,7 @@ var AccountPoolManager = class {
|
|
|
28207
28486
|
acc.quotas = {};
|
|
28208
28487
|
delete acc.authRequired;
|
|
28209
28488
|
delete acc.authError;
|
|
28489
|
+
delete acc.authMarkedAt;
|
|
28210
28490
|
this.persist();
|
|
28211
28491
|
}
|
|
28212
28492
|
clearAuthRequired(id) {
|
|
@@ -28214,6 +28494,7 @@ var AccountPoolManager = class {
|
|
|
28214
28494
|
if (!acc) return;
|
|
28215
28495
|
delete acc.authRequired;
|
|
28216
28496
|
delete acc.authError;
|
|
28497
|
+
delete acc.authMarkedAt;
|
|
28217
28498
|
this.persist();
|
|
28218
28499
|
}
|
|
28219
28500
|
setPrimaryAccount(id) {
|
|
@@ -28291,6 +28572,7 @@ var AccountPoolManager = class {
|
|
|
28291
28572
|
if (acc.authRequired) {
|
|
28292
28573
|
delete acc.authRequired;
|
|
28293
28574
|
delete acc.authError;
|
|
28575
|
+
delete acc.authMarkedAt;
|
|
28294
28576
|
}
|
|
28295
28577
|
if (acc.cooldowns[family]) delete acc.cooldowns[family];
|
|
28296
28578
|
this.persist();
|
|
@@ -28820,9 +29102,19 @@ function readWindowsCredentialToken() {
|
|
|
28820
29102
|
}
|
|
28821
29103
|
var QuotaService = class {
|
|
28822
29104
|
pool;
|
|
29105
|
+
globalProxy;
|
|
28823
29106
|
preferredEndpointIndex = 0;
|
|
28824
|
-
|
|
29107
|
+
/**
|
|
29108
|
+
* @param globalProxy resolves the plugin-wide proxy URL; used when the
|
|
29109
|
+
* account has no dedicated proxyUrl. Injected so tests stay network-free.
|
|
29110
|
+
*/
|
|
29111
|
+
constructor(pool, globalProxy = () => "") {
|
|
28825
29112
|
this.pool = pool;
|
|
29113
|
+
this.globalProxy = globalProxy;
|
|
29114
|
+
}
|
|
29115
|
+
/** Dedicated account proxy wins, then the global config proxy. */
|
|
29116
|
+
proxyFor(account) {
|
|
29117
|
+
return account.proxyUrl || this.globalProxy() || void 0;
|
|
28826
29118
|
}
|
|
28827
29119
|
getTokenFilePath(account) {
|
|
28828
29120
|
const home = account.systemHome || !account.dir ? homedir() : account.dir;
|
|
@@ -28899,7 +29191,7 @@ var QuotaService = class {
|
|
|
28899
29191
|
if (!tok) return null;
|
|
28900
29192
|
if (tok.accessToken && (!tok.expiryMs || tok.expiryMs > Date.now() + 6e4)) return tok.accessToken;
|
|
28901
29193
|
if (tok.refreshToken) try {
|
|
28902
|
-
const refreshed = await refreshTokens(tok.refreshToken, account
|
|
29194
|
+
const refreshed = await refreshTokens(tok.refreshToken, this.proxyFor(account));
|
|
28903
29195
|
if (refreshed?.access_token) {
|
|
28904
29196
|
this.persistRefreshedToken(account, {
|
|
28905
29197
|
access_token: refreshed.access_token,
|
|
@@ -28910,7 +29202,7 @@ var QuotaService = class {
|
|
|
28910
29202
|
}
|
|
28911
29203
|
} catch (err) {
|
|
28912
29204
|
const errMsg = String(err);
|
|
28913
|
-
if (
|
|
29205
|
+
if (shouldMarkAuthRequired(void 0, errMsg)) this.pool.markAuthRequired(account.id, errMsg);
|
|
28914
29206
|
}
|
|
28915
29207
|
return tok.accessToken || null;
|
|
28916
29208
|
}
|
|
@@ -29006,13 +29298,13 @@ var QuotaService = class {
|
|
|
29006
29298
|
const accessToken = await this.getValidAccessToken(account);
|
|
29007
29299
|
if (!accessToken) return null;
|
|
29008
29300
|
if (force) {
|
|
29009
|
-
const info = await this.fetchUserInfo(accessToken, account
|
|
29301
|
+
const info = await this.fetchUserInfo(accessToken, this.proxyFor(account));
|
|
29010
29302
|
if (info?.email) email = info.email;
|
|
29011
29303
|
}
|
|
29012
29304
|
if (email && email !== account.email) this.pool.resetAccountIdentity(account.id, email);
|
|
29013
|
-
const [summary, discovered] = await Promise.all([this.fetchQuotaSummary(accessToken, account
|
|
29305
|
+
const [summary, discovered] = await Promise.all([this.fetchQuotaSummary(accessToken, this.proxyFor(account)), this.fetchAvailableModels(accessToken, this.proxyFor(account))]);
|
|
29014
29306
|
if (!email) {
|
|
29015
|
-
const info = await this.fetchUserInfo(accessToken, account
|
|
29307
|
+
const info = await this.fetchUserInfo(accessToken, this.proxyFor(account));
|
|
29016
29308
|
if (info?.email) email = info.email;
|
|
29017
29309
|
}
|
|
29018
29310
|
if (!summary && (!discovered || !discovered.models)) return null;
|
|
@@ -29106,7 +29398,7 @@ var QuotaService = class {
|
|
|
29106
29398
|
const detected = detectEmailFromAgyLogs(acc.systemHome || !acc.dir ? homedir() : acc.dir);
|
|
29107
29399
|
if (detected && detected !== acc.email) this.pool.resetAccountIdentity(acc.id, detected);
|
|
29108
29400
|
}
|
|
29109
|
-
accounts = accounts.filter(shouldPollAccount);
|
|
29401
|
+
accounts = accounts.filter((acc) => shouldPollAccount(acc) || needsAuthReverify(acc));
|
|
29110
29402
|
}
|
|
29111
29403
|
await Promise.allSettled(accounts.map((acc) => this.refreshAccountQuota(acc, force)));
|
|
29112
29404
|
}
|
|
@@ -29347,7 +29639,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
29347
29639
|
const version = () => versionCache;
|
|
29348
29640
|
const store = new SessionStore(join(stateDir(), "sessions.json"));
|
|
29349
29641
|
const pool = new AccountPoolManager();
|
|
29350
|
-
const quota = new QuotaService(pool);
|
|
29642
|
+
const quota = new QuotaService(pool, () => getConfig().proxyUrl);
|
|
29351
29643
|
const semaphore = new Semaphore(() => getConfig().maxConcurrent);
|
|
29352
29644
|
const getDiscoveryEnv = (account) => {
|
|
29353
29645
|
const cfg = getConfig();
|
|
@@ -29360,14 +29652,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
29360
29652
|
ANTIGRAVITY_DISABLE_TELEMETRY: "1"
|
|
29361
29653
|
} : {},
|
|
29362
29654
|
...account && account.dir ? isolatedHomeEnv(account.dir) : {},
|
|
29363
|
-
...account?.proxyUrl
|
|
29364
|
-
ALL_PROXY: account.proxyUrl,
|
|
29365
|
-
HTTPS_PROXY: account.proxyUrl,
|
|
29366
|
-
HTTP_PROXY: account.proxyUrl,
|
|
29367
|
-
all_proxy: account.proxyUrl,
|
|
29368
|
-
https_proxy: account.proxyUrl,
|
|
29369
|
-
http_proxy: account.proxyUrl
|
|
29370
|
-
} : {}
|
|
29655
|
+
...proxyEnvFor(account?.proxyUrl || cfg.proxyUrl || void 0)
|
|
29371
29656
|
};
|
|
29372
29657
|
};
|
|
29373
29658
|
const catalog = new ModelCatalog(async (signal) => {
|
|
@@ -29397,6 +29682,9 @@ function apply(ctx, entryConfig = {}) {
|
|
|
29397
29682
|
if (swept > 0) log("swept " + swept + " stale staging dir(s)");
|
|
29398
29683
|
const logsSwept = pool.sweepOldLogs(getConfig().logRetentionDays);
|
|
29399
29684
|
if (logsSwept > 0) log("swept " + logsSwept + " old log file(s)");
|
|
29685
|
+
let kcProvisioned = 0;
|
|
29686
|
+
for (const acc of pool.getAccounts()) if (acc.dir && ensureIsolatedKeychain(acc.dir)) kcProvisioned++;
|
|
29687
|
+
if (kcProvisioned > 0) log("provisioned isolated keychains for " + kcProvisioned + " pool account(s)");
|
|
29400
29688
|
const adapter = new AgyAdapter({
|
|
29401
29689
|
getConfig,
|
|
29402
29690
|
catalog,
|
|
@@ -29617,7 +29905,8 @@ function apply(ctx, entryConfig = {}) {
|
|
|
29617
29905
|
const body = await readBody(req);
|
|
29618
29906
|
const homeDir = typeof body.homeDir === "string" ? body.homeDir : void 0;
|
|
29619
29907
|
const accountId = typeof body.accountId === "string" ? body.accountId : void 0;
|
|
29620
|
-
const
|
|
29908
|
+
const proxyUrl = typeof body.proxyUrl === "string" && body.proxyUrl.trim() !== "" ? body.proxyUrl.trim() : getConfig().proxyUrl.trim() || void 0;
|
|
29909
|
+
const st = await auth.begin(homeDir, accountId, proxyUrl);
|
|
29621
29910
|
sendJson(res, 200, st);
|
|
29622
29911
|
})();
|
|
29623
29912
|
}
|
|
@@ -29683,7 +29972,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
29683
29972
|
}
|
|
29684
29973
|
const body = await readBody(req);
|
|
29685
29974
|
const alias = typeof body.alias === "string" ? body.alias : void 0;
|
|
29686
|
-
const proxyUrl = typeof body.proxyUrl === "string" ? body.proxyUrl : void 0;
|
|
29975
|
+
const proxyUrl = typeof body.proxyUrl === "string" && body.proxyUrl.trim() !== "" ? body.proxyUrl.trim() : getConfig().proxyUrl.trim() || void 0;
|
|
29687
29976
|
const st = await poolAuth.begin(alias, proxyUrl);
|
|
29688
29977
|
sendJson(res, st.ok ? 200 : 500, st);
|
|
29689
29978
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.41",
|
|
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",
|