dsh-agy-link 0.4.44 → 0.4.49

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 CHANGED
@@ -1,5 +1,169 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.49 (2026-10-14)
4
+
5
+ ### English
6
+
7
+ - **Removal: Gemini Web (chat.gemini.google.com) account support is gone.** The whole web-login feature was removed per user request: the reverse-engineered web client, isolated-profile browser capture, the five `/plugins/agy-link/pool/*` web routes, the web execution branch in the adapter, web quota handling, all `web.*` UI (type switcher, browser picker, capture progress, test/re-login buttons, Web badge) and locale strings. Legacy `gemini-web` entries in `pool.json` are dropped automatically on load, with dangling active/primary pointers cleaned up. Existing AGY accounts are untouched.
8
+ - **Fix (macOS): keychain password popups are gone for good.** `ensureIsolatedKeychain` never touches the user's global `default-keychain` / search list anymore (the per-account isolated keychain under the account HOME is enough), and `readMacKeychainToken` now targets the real `~/Library/Keychains/login.keychain-db` explicitly instead of whatever the hijacked default pointed at. Stale locked keychains from the pre-0.4.45 random-password era should be deleted once — they are unanswerable prompts by construction.
9
+ - **Fix: a dead agy child can no longer crash the whole DSH host.** EPIPE on child stdin arrives asynchronously as an `error` event (the old `try/catch` only guarded sync throws); `startAgyProcess` now attaches a no-op error handler so a `WriteWrap.onWriteComplete write EPIPE` uncaughtException — and the crash dialog — cannot happen from our spawns.
10
+ - **Fix: "needs re-login" no longer shows for healthy accounts.** The Gemini-Web error path flagged accounts on bare `401`/`403`/`unauthorized` substrings, quarantining healthy slots whenever transport noise happened to contain those digits; flagged accounts skip background polling, so only a manual sync could disprove the flag. The web branch is gone entirely, and the auth-flag classifier keeps its transport-noise exemptions.
11
+ - **Fix: the header badge no longer flashes "AGY (0)" with a grey dot.** The status snapshot is now persisted in `localStorage` and preloaded on mount (stale-while-revalidate), so session switches render the last known account immediately; the truly-cold first render shows a neutral `AGY` placeholder instead of a misleading zero count.
12
+
13
+ ### 中文 (Chinese)
14
+
15
+ - **移除:Gemini 网页端(chat.gemini.google.com)账号支持。** 应用户要求整体移除网页登录功能:逆向网页客户端、独立 Profile 浏览器捕获、5 个 `/plugins/agy-link/pool/*` 网页路由、adapter 中的网页执行分支、网页配额处理、全部 `web.*` 界面(类型切换、浏览器选择、捕获进度、测试连接/重新登录按钮、Web 徽章)与语言串。`pool.json` 中遗留的 `gemini-web` 账号会在加载时自动剔除,悬空的 active/primary 指针同步清理;现有 AGY 账号不受影响。
16
+ - **修复(macOS):钥匙串密码弹窗彻底消失。** `ensureIsolatedKeychain` 不再触碰用户全局的 `default-keychain` / 搜索列表(账号 HOME 下的隔离钥匙串已足够);`readMacKeychainToken` 显式指向真实的 `~/Library/Keychains/login.keychain-db`,不再受被篡改默认值影响。0.4.45 之前随机密码时代遗留的锁定钥匙串需手动删除一次——它们天然是无法回答的弹窗。
17
+ - **修复:agy 子进程死亡不再拖垮整个 DSH 宿主。** 子进程 stdin 上的 EPIPE 以异步 `error` 事件到达(旧的 `try/catch` 只能挡同步异常);`startAgyProcess` 现在挂载 no-op 错误监听,`WriteWrap.onWriteComplete write EPIPE` 不可捕获异常与崩溃弹窗不会再由我们的拉起触发。
18
+ - **修复:健康账号不再显示"需重新登录"。** 网页版错误路径曾用裸 `401`/`403`/`unauthorized` 子串拉黑账号,传输噪音消息里碰巧带这些数字就隔离健康槽位;被标记账号会被后台轮询跳过,只有手动同步能翻案。该分支已整体移除,拉黑分类器保留传输噪音豁免。
19
+ - **修复:顶部徽章不再闪烁灰色 `AGY (0)`。** 状态快照现在持久化到 `localStorage` 并在挂载时预加载(stale-while-revalidate),切换会话立即显示上次的账号;真正冷启动的首帧显示中性 `AGY` 占位,而不是误导性的 0 计数。
20
+
21
+ ## 0.4.48 (2026-10-13)
22
+
23
+ ### English
24
+
25
+ - **Feature: Google Account email retrieval & verification.** Enhanced browser login CDP extraction and `extractSnlm0e` regex across multi-language Google interfaces (including Chinese `Google 账号 / 帐号`, `WIZ_global_data`, and custom workspace domains). Verified emails are automatically saved and displayed as an email badge on account cards upon addition and connection testing.
26
+ - **Feature: Isolated per-account browser profiles.** Each Gemini Web account now runs in a dedicated, isolated browser profile directory (`~/.dsh/agy-accounts/<accountId>/browser-profile`). Multiple accounts maintain independent cookie jars, session stores, and locks, eliminating session cross-contamination and allowing seamless multi-account management.
27
+ - **Feature: Dynamic Gemini Web build (`bl`) & update sniffing.** Live sniffing of Google's frontend build label (`bl`, e.g. `boq_assistant-bard-web-server_...`) directly from HTML during login, connection tests, and periodic probes, dynamically passed to `StreamGenerate` RPC. Stale sessions, cookie expirations, and token rotations are automatically detected, with immediate UI alerts and a dedicated re-login button.
28
+ - **Feature: Dedicated proxy URL for Antigravity OAuth login.** Added a per-account proxy URL configuration input to the Google Antigravity OAuth tab, consistent with Gemini Web accounts, ensuring reliable authentication in domestic network environments.
29
+ - **Feature: Direct model mapping & removed `gemini-3.1-pro` fallback.** Removed the fictional `gemini-3.1-pro` fallback and purged it from `DEFAULT_FALLBACK_MODELS`. Requests directly preserve and map to the exact model selected by the user.
30
+ - **Feature: Real-time dynamic model catalog.** Wired `ModelCatalog` updates to notify DSH via `notifyPicker()` (`llm/adapters-updated`), and integrated automatic background refreshes so newly discovered `agy` models immediately appear in the DSH model picker without restarting DSH.
31
+ - **Feature: Active model synchronization with DSH composer picker.** When model auto-failover, default resolution, or Gemini Web mapping switches the active model, the new model is synchronized back to the active DSH session (`model/selection`), updating the model chip in the bottom-right corner of the composer.
32
+ - **Fix: Eliminated modal screen flickering and refresh loop.** Implemented singleton portal mounting (`registerModalInstance` / `unregisterModalInstance`), moved global modal styles out of the React render tree into a one-time `<head>` injection, and added payload equality guards (`isStatusEqual`) on polling intervals to eliminate duplicate renders and flickering.
33
+
34
+ ### 中文 (Chinese)
35
+
36
+ - **新增特性:Google 账号邮箱回显与连通性验证。** 全面强化 CDP 浏览器捕获与 `extractSnlm0e` 正则解析,支持中文界面(`Google 账号 / 帐号`)、`WIZ_global_data` 及任意域名 Google 邮箱;添加与测试连接时均自动保存并回显邮箱徽章于卡片上。
37
+ - **新增特性:每个账号完全独立的浏览器隔离 Profile。** 每个 Gemini 网页端账号分配并持久化独占的 profile 目录(`~/.dsh/agy-accounts/<accountId>/browser-profile`);多账号各自拥有独立 Cookie、会话与锁文件,多账号并行登录或切换互不干扰、互不污染。
38
+ - **新增特性:动态嗅探 Gemini 官方最新构建版本(`bl`)与状态。** 实时从页面嗅探 Google 最新前端构建版本号(`bl`,如 `boq_assistant-bard-web-server_...`)并动态传递至 `StreamGenerate` RPC;自动捕获 Session 状态变化与凭据失效,卡片提供一键「重新登录」按钮。
39
+ - **新增特性:Google Antigravity(OAuth 授权)独立代理配置。** 在 Google Antigravity 添加弹窗中新增独立代理 URL 输入框,与网页端账号保持一致,国内网络环境下直连或代理灵活切换。
40
+ - **新增特性:模型精准直接映射与彻底移除 `gemini-3.1-pro`。** 彻底移除虚构的 `gemini-3.1-pro` 强制降级逻辑,并从系统默认候选模型中删除;用户选择什么模型就直接映射到什么模型,原汁原味。
41
+ - **新增特性:模型列表无需重启 DSH 实时刷新。** 将 `ModelCatalog` 更新与 `notifyPicker()` (`llm/adapters-updated`) 连通,并引入定期检测与触发式刷新;agy 本地更新模型后,DSH 输入框模型选择器即时自动呈现最新列表,无需重启应用。
42
+ - **新增特性:自动切模型实时回显到 DSH 输入框右下角。** 当额度耗尽自动切模型、默认模型解析或网页端模型映射发生时,自动向当前会话广播同步 `model/selection`,使 DSH 右下角模型选框即时同步回显当前真实运行的模型。
43
+ - **优化与修复:彻底解决控制台频繁刷新与闪屏问题。** 引入弹窗单例守卫(`registerModalInstance` / `unregisterModalInstance`)防止多头部重复挂载,将全局样式抽离至 `<head>` 单次注入避免全页重排,并在轮询时加入状态深比较(`isStatusEqual`)杜绝无效渲染。
44
+
45
+ ## 0.4.47 (2026-10-13)
46
+
47
+ ### English
48
+
49
+ - **Merge (2026-10-13): the recovered working tree is back on top of 0.4.46, and Windows pool-credential isolation is real again.** Upstream's line stays the baseline (adapter spawn-window slot ownership, async keychain/Credential-Manager reads, desktop thinking visibility #43, the vendored mermaid renderer with no CDN fallback plus its error-bomb sweep, model visibility, pool/data hardening). Restored on top of it: the Windows Credential Manager handoff described below, the local `/plugins/agy-link/media` route with the image lightbox and decision chips, Windows system-proxy auto-detection (WinINet registry), the pool corruption diagnostics/repair scripts, and the `src/host/wincred.ts` primitives. Merge calls worth knowing: `runner.ts`, `assets/mermaid.min.js`, `src/client/mermaid.ts` and the proxy/keychain test take upstream (equivalent fixes, already hardened — the duplicate mermaid renderer and its tests were dropped); `quota.ts` keeps upstream's async read *and* the durable-store-first primary resolution; the `credentialIsolationLimited` warning (status field, adapter log, settings banner, four locale strings) is gone now that rotation isolates credentials; and the real Credential Manager slot is never touched under `node:test`.
50
+ - **Fix (critical): multi-account pools no longer cross-contaminate credentials on Windows, and quota failover works again.** On Windows, `agy.exe` resolves its Google credential from ONE process-global store (Credential Manager target `gemini:antigravity`); `HOME`/`USERPROFILE` are ignored, so a per-account directory does not isolate credentials the way a per-account Keychain does on macOS. The previous handoff logic corrupted a real pool: all four accounts ended up holding the primary's refresh token, every quota row reported the same identity, and clicking "refresh/sync" appeared to replace the main account. Three defects were fixed:
51
+ - The handoff snapshotted "whatever is currently in the slot" as the primary backup. A secondary run could therefore overwrite the backup with a secondary credential, and the next restore installed that secondary as the system-wide login. The primary credential is now resolved from a durable, identity-VERIFIED store (`agy-accounts/.primary-credential.json`) and is never inferred from the live slot.
52
+ - The restore step wrote the slot's content back into the account's token file whenever it differed. While another account owned the slot this copied a FOREIGN credential into the account file. Write-back now requires that the slot still carries THAT account's refresh token; otherwise nothing is written and a warning is logged.
53
+ - Nothing serialized slot ownership, so interactive runs, background quota polls and manual refreshes raced. A process-wide FIFO mutex (`AsyncMutex`) now spans the entire run.
54
+ - **Add: a run is refused instead of silently using the wrong account.** `acquireCredentialSlot` throws `SlotUnavailableError` (surfaced as `AGY_ERROR`) when the slot cannot be handed back safely: the primary credential cannot be verified, the secondary has no credential, or the secondary holds the SAME grant as the primary. This stops a corrupted slot from quietly spending the primary's quota.
55
+ - **Fix: quota refresh can no longer relabel or overwrite an account.** An isolated account resolved to another slot's email is now quarantined (`authRequired`) instead of being relabeled; an isolated account whose stored credential equals the primary's is quarantined offline before any network call. The durable store is consulted before the volatile slot for the primary, so a mid-handoff slot can no longer make the primary row display a secondary's numbers.
56
+ - **Fix: refreshed tokens keep their refresh token and agy's nested on-disk shape.** The writer used to emit a flat `{access_token,expiry}` document when the file did not exist yet, silently DROPPING `refresh_token`. A credential without a refresh token has no stable identity across access-token rotations, which disabled every identity guard. The writer now always emits `{"token":{…},"auth_method":"consumer"}` and preserves the refresh token.
57
+ - **Add: identity comparison anchored on the OAuth grant.** `sameCredentialGrant()` requires a refresh token on both sides ("cannot prove it" means "not the same"); adoption of a primary credential now skips candidates without a refresh token so the durable store can never be degraded.
58
+ - **Add: corruption diagnostics and repair tooling.** `findDuplicateCredentialIdentities()` reports accounts sharing one login, and the boot log now warns loudly. `scripts/repair-pool.mts` (dry-run by default, `--apply` to write) restores the primary slot and quarantines overwritten accounts; `scripts/verify-credential-store.mts` verifies the store's live identity. Running `pnpm test` no longer touches the real pool directory — credential handling is anchored on `AccountPoolManager.dir` instead of a process-global path.
59
+ - **Fix: a malformed `mermaid` block no longer paints Mermaid's own "Syntax error in text" bomb into the page.** Mermaid's standalone renderer appends a temporary `<div id="d<id>">` to `document.body` and, when a diagram fails to parse, draws its built-in error diagram into that div *instead of throwing* — so the failure never reached our `catch`, and the bomb escaped the message card and floated detached at the end of the page. The runtime is now initialized with `suppressErrorRendering: true`, every block is pre-validated with `mermaid.parse(text, { suppressErrors: true })` before `render()` runs, and a sweep removes body-level error diagrams left behind by older client builds. Invalid or still-streaming diagrams simply keep their original code block, and an unchanged failing block is no longer re-parsed on every DOM mutation.
60
+
61
+ ### 中文
62
+
63
+ - **合并(2026-10-13):被舍弃的工作区内容重新叠加回 0.4.46,Windows 账号池凭据隔离真正恢复。** 以上游为主线(适配器 spawn 窗口槽位归属、异步钥匙串/凭据管理器读取、桌面端思考可见性 #43、无 CDN 的本地 mermaid 渲染器与其错误炸弹清理、模型可见性、账号池与数据加固),在其之上恢复:下述 Windows 凭据管理器交接、本地 `/plugins/agy-link/media` 路由与图片灯箱/决策芯片、Windows 系统代理自动探测(WinINet 注册表)、账号池串号诊断与修复脚本、以及 `src/host/wincred.ts` 原语。几处取舍:`runner.ts`、`assets/mermaid.min.js`、`src/client/mermaid.ts` 与 proxy/keychain 测试取上游(同类问题已被上游加固,重复的 mermaid 实现与其测试被删除);`quota.ts` 同时保留上游的异步读取与你的「持久化存储优先」主账号解析;轮巡已能真正隔离凭据,因此 `credentialIsolationLimited` 警告(状态字段、适配器日志、设置面板横幅、四语言文案)全部移除;`node:test` 下绝不触碰真实凭据管理器槽位。
64
+ - **修复(严重):Windows 下多账号池不再串号,额度耗尽切换恢复正常。** Windows 上 `agy.exe` 只从**一个全局凭据槽**(凭据管理器 `gemini:antigravity`)读取 Google 凭据,`HOME`/`USERPROFILE` 被忽略,因此「每账号独立目录」并不像 macOS 的独立钥匙串那样隔离凭据。旧的手递逻辑破坏了一个真实账号池:四个账号全部持有主账号的 refresh token、每一行额度都显示同一个身份、点击「刷新/同步」看起来会把主账号替换掉。本次修复三个缺陷:
65
+ - 旧逻辑把「当前槽里的内容」当作主账号备份快照,于是副账号运行会用自己的凭据覆盖备份,下次恢复就把副账号装成了系统级登录。现在主账号凭据来自**经过身份校验的持久化存储**(`agy-accounts/.primary-credential.json`),绝不从活动槽推断。
66
+ - 恢复阶段只要发现槽内容与账号文件不同就写回账号文件;当槽被别的账号占用时,这会把**别人的凭据**写进该账号文件。现在写回前必须确认槽仍持有**该账号自己的 refresh token**,否则不写并记录警告。
67
+ - 槽的归属完全没有串行化,交互运行、后台额度轮询、手动刷新互相竞争。现在由一个进程级 FIFO 互斥锁(`AsyncMutex`)覆盖整个运行周期。
68
+ - **新增:无法安全交接时直接拒绝运行,而不是悄悄用错账号。** `acquireCredentialSlot` 在以下情况抛出 `SlotUnavailableError`(表现为 `AGY_ERROR`):无法验证主账号凭据、副账号没有凭据、或副账号持有与主账号**相同的授权**。这阻止了被污染的槽偷偷消耗主账号额度。
69
+ - **修复:额度刷新不再改名或覆盖账号。** 独立账号解析出的邮箱若属于另一个槽,改为**隔离**(`authRequired`)而不是改名;独立账号的凭据与主账号相同的,在任何网络请求之前就离线隔离。主账号优先读取持久化存储而非易变的槽,中途被交接的槽再也无法让主账号行显示副账号的数字。
70
+ - **修复:刷新后的 token 保留 refresh token 与 agy 的嵌套磁盘格式。** 旧写入器在文件不存在时会写成扁平 `{access_token,expiry}`,**静默丢弃 `refresh_token`**。没有 refresh token 的凭据在 access token 轮换后就没有稳定身份,导致所有身份守卫失效。现在固定写入 `{"token":{…},"auth_method":"consumer"}` 并保留 refresh token。
71
+ - **新增:以 OAuth 授权为锚的身份比较。** `sameCredentialGrant()` 要求双方都有 refresh token(「无法证明」即视为「不同」);主账号凭据的采纳会跳过没有 refresh token 的候选,持久化存储因此不会再被降级。
72
+ - **新增:串号诊断与修复工具。** `findDuplicateCredentialIdentities()` 报告共享同一登录的账号,启动日志会明确告警。`scripts/repair-pool.mts`(默认 dry-run,加 `--apply` 才写入)恢复主账号槽并隔离被覆盖的账号;`scripts/verify-credential-store.mts` 校验存储的真实身份。`pnpm test` 不再触碰真实账号池——凭据处理改为锚定 `AccountPoolManager.dir` 而非进程级全局路径。
73
+ - **修复:语法错误的 `mermaid` 代码块不再把 Mermaid 自带的「Syntax error in text」炸弹图渲染到页面上。** Mermaid 独立版渲染器会向 `document.body` 追加一个临时 `<div id="d<id>">`,图表解析失败时它把内置错误图**画进这个容器而不是抛异常**——失败因此永远到不了我们的 `catch`,炸弹图逃出消息卡片、孤立地漂在页面末尾。现在运行时以 `suppressErrorRendering: true` 初始化,每个代码块在 `render()` 之前先用 `mermaid.parse(text, { suppressErrors: true })` 预校验,并会清理旧客户端版本遗留在 body 层的错误图。语法错误或仍在流式输出的图表只保留原始代码块,同一份失败代码也不再因每次 DOM 变更而反复解析。
74
+
75
+ ## 0.4.46 (2026-10-12)
76
+
77
+ ### English
78
+
79
+ - **Fix (review cycle 2026-10-12): concurrency-slot leak in the adapter spawn window.** `stream()` acquired a `maxConcurrent` slot and only released it from the process-outcome closure — a caller that abandoned the generator while it was suspended on the burst-spacing throttle (steer abort, DSH closing the stream) dropped the release closure, and each incident permanently leaked one slot until every future run dead-locked in the queue. The spawn window now owns the slot through try/finally, an already-aborted signal short-circuits before spawn, and an abort that lands during the window kills the process immediately instead of running a full turn nobody reads.
80
+ - **Fix (review cycle): a stale pool-auth flow could kill the new login or commit a ghost account.** `finishWithCode` resolved against `this.flow` without checking WHICH flow the callback belonged to: flow A's late `exchangeCode` resolution during flow B either failed B with `invalid_state` (deleting B's staging dir) or, worse, wrote A's token file into A's freshly cleaned dir and committed a one-file ghost account. Settlement is now flow-identity-checked.
81
+ - **Fix (review cycle): long agy tools no longer look like a frozen session.** ACTIVE tool envelopes now emit a one-time `[agy tool running: …]` note into the reasoning block, so a ten-minute maven build shows live progress instead of silence (the 5-minute heartbeat only ever reached the logs). Subagent reasoning snapshots also stop duplicating the whole transcript (suffix-delta like the thinking branch), and a tool-cut span forwards the last per-call usage sample instead of a hard 0/0 that reset DSH's token meter on every tool hop.
82
+ - **Fix (review cycle): synthetic-message filtering can no longer eat the user's real message.** When the sniffed synthetic-looking text is the ONLY user content of the trailing span (a pasted `<system-reminder>` block being the actual question), the bridge now forwards it instead of failing with "request carries no user text" — while an image-only turn still never digs up an older question.
83
+ - **Fix (review cycle): pool/data hardening.** `deleteAccount` only ever `rmSync`s directories under the pool base dir (a hand-edited `dir: "~"` used to be deleted recursively); `pool.json` persists through a known-ids merge so web + headless stop erasing each other's accounts (corrupt files are backed up before defaults overwrite them, and persist failures are surfaced on stderr); `/agy add-account` and `POST /pool/add` quarantine the new slot with `authRequired` until the terminal login is verified, so the scheduler can no longer route real runs to an account nobody signed into; a drained quota window with an UNPARSEABLE reset now counts as exhausted in eligibility and rotation instead of "already reset".
84
+ - **Fix (review cycle): quota/OAuth robustness.** All Google endpoint calls carry a 10s default timeout (a hung refresh-token/quota endpoint used to park `/agy refresh-quota` for minutes); keychain/Secret Service/Credential-Manager reads are async (`execFileSync` up to 8s used to freeze the whole DSH event loop on every poll); concurrent `getValidAccessToken` calls for one account share one in-flight refresh; quota fractions are clamped to [0,1] with NaN rejected; refreshed token files are written 0600.
85
+ - **Fix (review cycle): auth flow lifecycle.** Cancelling during the URL wait now resolves the pending `begin()` (the `/plugins/agy-link/auth` HTTP route used to hang forever), a superseded `begin()` can no longer overwrite the newer flow's state, and the probe/settle timers are cleaned up.
86
+ - **Fix (review cycle): agy conversation DB reads.** A missing `sqlite3` binary or oversized/locked DB used to re-spawn sqlite3 on EVERY step event with the failure silently swallowed (cache still marked loaded, so the storm never converged); failures now back off (ENOENT for the full window, transient for 300ms), log once, and concurrent readers share one in-flight load. The DB copy is taken into a private `mkdtemp` dir with 0600 instead of a predictable /tmp path.
87
+ - **Fix (review cycle): misc.** The OAuth callback page HTML-escapes the reflected `error` parameter; Windows `openBrowser` refuses URLs containing quotes/newlines before the cmd string build; doctor redaction covers `1//0…` refresh tokens, `AIza…` keys and credentialed proxy URLs, and the config dump scrubs `proxyUrl` credentials; `/agy pool` prints proxy URLs redacted; fallback-source model discovery re-probes at most once per minute instead of spawning `agy models` on every list; fallback conversation-id discovery watches the pool account's isolated HOME; mermaid's CDN fallback is removed entirely (floating-version unsigned script in the app origin) and the mermaid/reasoning overlay observers are disposed through `ctx.effect`.
88
+ - **Add: token-speed statistics.** Every recorded event now carries its arrival time; per-step generation windows (≥500 ms) yield `lastTps`/output-weighted `avgTps`/output-token totals on the recording, surfaced through `/agy status` ("last run tokens: N output tokens — avg X tok/s (last generation step: Y tok/s)") and the `lastRun` status payload.
89
+ - **Fix (issue #43): thinking and tool activity are no longer invisible on desktop hosts.** Two compounding causes: (1) thought prose comes from the agy conversation SQLite DB, and the reader required the external `sqlite3` CLI — which GUI/desktop hosts often lack on PATH (macOS GUI apps do not inherit the shell PATH; Windows never ships one). The reader now prefers Node's built-in `node:sqlite` (no external binary) and keeps the CLI as fallback, so reasoning text resolves on desktop hosts. (2) When prose is unavailable, high-effort turns rendered nothing at all; a banner-only `[agy thinking turn · N thinking tokens]` chip now streams before the first visible text (and on thinking-only turns), with the real prose appended seamlessly when the DB resolves later — no duplicate banner, no dropped text, and no clutter on tool-heavy steps after text already streamed.
90
+ - **Fix (issue #45): Windows pool-credential isolation is now an explicit, visible limitation.** agy on Windows stores its login in the system-wide Credential Manager slot `gemini:antigravity` (go-keyring); HOME isolation moves files only, so every isolated pool account still authenticates — and bills — as the system login. The plugin now (1) exposes `credentialIsolationLimited` in `/plugins/agy-link/status`, rendered as a warning banner in the settings panel, (2) logs once per isolated account when it is selected on Windows, and (3) documents the boundary (and why per-run `CredWrite` slot-swapping is not safe with `maxConcurrent > 1`) in KNOWN-GAPS.md. Full isolation needs an upstream agy change (per-HOME keyring namespace or file fallback when the keyring slot is empty).
91
+
92
+
93
+ - **Fix: the stuck-run heartbeat no longer lies about how long the run has been silent.** With no tool step open the silence clock was seeded from `Date.now()` over an empty map, so the every-5-minutes log permanently claimed "no new output for 1m" while the run had actually been quiet for hours. It now reports the time since the last stdout line (`lastOutputAt`), which is the number a human needs when deciding whether to press Stop.
94
+ - **Fix: `agy_tool` no longer blames a "recycled" output for a cursor that never pointed at one.** An out-of-range step index (or a non-tool event) and a genuinely evicted recording both used to return the "recorded output is no longer resident" notice — telling the model that output was lost when it was never recorded at all. The degraded message now states the actual reason ("outside this recording — not recorded yet / not a completed tool step" vs. "the in-memory recording was recycled").
95
+ - **Fix: account failover now actually continues the session — including after a completed tool step (the "quota exhausted, turn lost" bug).** DSH re-issues the failed step, and a step that failed inside a tool continuation carried a trailing tool result, so it was resumed from the SETTLED recording and re-emitted the identical failure: a quota/region/auth refusal after any tool call ended the turn while a healthy account was sitting idle (reproduced offline against `test/fake-agy.mjs`). The continuation branch now refuses to re-drive a settled recording whose failure is account-scoped, forgets it, and re-assembles on the next account — inside the same step, with no visible interruption. The failover is bounded (at most `enabled accounts - 1` re-spawns per session per 2 minutes) and never applies to request-level failures, so a deterministic error cannot loop through the pool re-running the user's tools.
96
+ - **Fix: transport-shaped "not logged in" is no longer reported as AUTH (the "API 密钥无效 / API key is invalid" hunt).** agy reports "not logged into Antigravity" both for a real sign-out and for a healthy login whose token it could not VALIDATE (live 2026-10-10 14:17: the retry account's token had expired 9 minutes earlier and its refresh died on `Post https://oauth2.googleapis.com/token: EOF`, next to TLS handshake timeouts). The adapter classified that as `AUTH`, which (a) ended the turn on a healthy account, (b) flagged the account for re-login, and (c) made DSH render its `message.failure.auth` label — "API key is invalid" — for a credential that was never the problem. Transport evidence in the auth-shaped stdout banner, stderr or the result envelope now yields a retryable `NETWORK` failure that rotates to the next account and never quarantines a login.
97
+ - **Fix: the retry budget follows the pool, and a sign-out can continue on another account.** `maxRetries` was a flat 1, so a 3-account pool could never reach the third account — and the live incident needed exactly that (primary ELIGIBILITY → second account "not signed in" → turn lost, though the third was healthy). The budget is now `min(enabled accounts - 1, 2)`, and `AUTH` is retryable when at least one other account exists (the failed one is flagged `authRequired` and leaves rotation).
98
+ - **Fix: soft capacity refusals rotate instead of dead-ending the turn.** "model overloaded" / "experiencing high traffic" used to be a deterministic `AGY_ERROR` — terminal, no rotation, no retry — so a turn died while another account could serve it. They are now `OVERLOADED`: retryable, pointer rotated, and deliberately WITHOUT a cooldown (nothing was spent). The hard-quota matcher also recognises real phrasings it used to miss (`Resource has been exhausted (e.g. check quota).`, `You have exhausted your quota`, `out of quota`, `check quota`), which previously fell through to a bare "agy exited with code 1" on the SAME account.
99
+ - **Fix: the idle watchdog no longer kills a run that is demonstrably working.** 12 of the 37 failures in one week of real logs were exactly `agy run was idle for 600000ms without output`, each costing a turn and a resend. stderr now counts as liveness (agy prints progress there), and once a run has produced ANY output the long budget applies (`longToolIdleMinutes`, default 240m) — before the first event the strict `timeoutMs` stays, which is the genuinely wedged / Google-unreachable shape. The 5-minute heartbeat now also covers a silent-but-alive run instead of only open tool steps, so the user can see it is waiting and press Stop.
100
+ - **Fix: prompts no longer re-send tool output the live agy conversation already owns, and synthetic harness telemetry is filtered.** A bound conversation was piping prior tool results back into `-p` (a live command line was captured carrying a 6.4 KB Godot simulation dump) — stale output ahead of the actual question, which is one of the ways the model ends up answering the previous work. Tool payloads are now dropped when the conversation already has them (they still ride the prompt on a fresh conversation, where agy has never seen them). Separately, DSH runtime telemetry messages injected with `role: 'user'` (`time-context`, `runtime-context`, `<system-reminder>`) are filtered so timestamps never overwrite the human prompt, and an image-only (text-less) newest message no longer falls back to older questions.
101
+ - **Fix: an interrupted turn can no longer make agy answer the message you interrupted.** DSH persists the partial reply of a Stop/steer as a normal assistant message, so the plugin treated the interrupted user turn as answered agy-native history while agy's server-side turn had no finalized reply — the next prompt was just the new message, and the model answered the interrupted one (live: `session-c65a826e` turns 17→19, the user re-asked verbatim with "这个咋不回答呢"). The binding is now flagged `interrupted` when a run is aborted or times out, the next prompt carries an explicit "do not continue that unfinished work" boundary, and a successful turn clears the flag. Images staged by an interrupted run are no longer marked delivered either (re-sending the same screenshot after a Stop was silently deduped away and an image-only follow-up failed with "request carries no user text or images").
102
+ - **Fix: a run that already produced its result is no longer killed by the next call.** Steer preemption aborted a process that had already streamed its answer (recording a false `ABORTED` and cutting agy mid-finalize); it now waits briefly for a natural exit and only preempts a still-running turn. The bounded settle wait reports when it expires, and a superseded run's late write-back can no longer regress `lastMessageCount` / `sentImageIds`.
103
+ - **Fix: pool and binding robustness.** A partial `pool.json` (account missing `cooldowns`/`quotas`) threw a `TypeError` inside `selectAccount`, so every turn failed with an opaque internal error and no failover — records are normalized on load. Rate limits no longer delete the `(session, account)` binding, so a resumable native conversation is not thrown away for a 15-minute cooldown (only a real sign-out or an agy-side "conversation not found" invalidates it). Conversation adoption from a live run is scoped to the SAME account (adopting account A's id under account B produced a wasted "conversation not found" turn). `sessions.json` now performs the dirty-key merge its own docs promised, so the web and headless hosts stop clobbering each other's bindings. A failed turn reports the last per-call context sample instead of a hard-coded 0/0. ADR-012 no longer claims a flat "zero interruption" fallback with a 10-minute cooldown.
104
+
105
+ ### 中文 (Chinese)
106
+
107
+ - **修复(2026-10-12 审查周期):适配器 spawn 窗口的并发槽位泄漏。** `stream()` 取得 `maxConcurrent` 槽位后只由进程结算闭包释放——调用方在突发节流 sleep 处放弃生成器(插话中止 / DSH 关闭流)会直接丢掉 release 闭包,每次事故永久泄漏一个槽位,累积后所有后续请求在队列里死锁。现在 spawn 窗口用 try/finally 持有槽位;已中止的 signal 会在 spawn 前短路;窗口内到达的中止会立刻杀进程,而不是跑完一轮没人读的 turn。
108
+ - **修复(审查周期):滞后的 pool-auth 流程可能杀死新登录或提交幽灵账号。** `finishWithCode` 直接对 `this.flow` 结算、不校验回调属于哪个 flow:用户取消 A 并开始 B 后,A 的 `exchangeCode` 迟到返回,要么以 `invalid_state` 杀掉 B(连带删掉 B 的 staging 目录),要么把 A 的 token 文件写进刚清理的 A 目录并提交一个只剩 token 文件的幽灵账号。现在结算带 flow 身份校验。
109
+ - **修复(审查周期):长时间 agy 工具不再让会话看起来卡死。** ACTIVE 工具信封现在会在推理块输出一次性 `[agy tool running: …]` 进行中注解——十分钟的 maven 构建期间界面有实时进展,而不是只有日志里的 5 分钟心跳。subagent 推理快照改用与 thinking 分支相同的 suffix-delta,不再整段重复;工具切割 span 转发最后一次 per-call 用量样本,不再用硬编码 0/0 在每个工具 hop 重置 DSH 的 token 计量。
110
+ - **修复(审查周期):合成消息过滤不再吞掉用户的真实消息。** 当被嗅探命中的文本是尾随 span 里唯一的用户内容(用户粘贴 `<system-reminder>` 块本身就是要问的问题),桥接现在会转发它,而不是报「request carries no user text」;纯图片轮次依旧不会回捞更早的问题。
111
+ - **修复(审查周期):账号池与数据加固。** `deleteAccount` 只会删除池 baseDir 下的目录(手改 `dir: "~"` 曾会被递归删除);`pool.json` 持久化改为 known-ids 合并,web 与 headless 不再互相清掉对方账号(损坏文件先备份再被默认值覆盖,写盘失败打到 stderr);`/agy add-account` 与 `POST /pool/add` 在登录验证通过前用 `authRequired` 隔离新槽位,调度器无法再把真实 run 路由到没人登录过的账号;额度耗尽但 reset 时间不可解析时按「已耗尽」参与资格判定与轮换,而不是「已重置」。
112
+ - **修复(审查周期):配额 / OAuth 健壮性。** 所有 Google 端点调用默认 10s 超时(挂死的 refresh-token / 配额端点曾让 `/agy refresh-quota` 卡死数分钟);钥匙串 / Secret Service / Windows 凭据管理器读取全部异步化(`execFileSync` 最长 8s 曾在每个轮询周期冻结整个 DSH 事件循环);同账号并发 `getValidAccessToken` 共享一次 in-flight 刷新;配额比例钳制到 [0,1] 并拒绝 NaN;刷新后的 token 文件以 0600 落盘。
113
+ - **修复(审查周期):登录流程生命周期。** URL 等待期间取消会 resolve 挂起的 `begin()`(`/plugins/agy-link/auth` HTTP 路由曾永久挂起);被取代的 `begin()` 不会再覆盖新流程状态;probe/settle 计时器均已清理。
114
+ - **修复(审查周期):agy 会话数据库读取。** 缺 `sqlite3` 或库超大/被锁时,之前每个 step 事件都会重新 spawn 一次 sqlite3 且失败被静默吞掉(缓存仍标记 loaded,风暴永不收敛);现在失败退避(ENOENT 按整个缓存窗口、瞬时失败 300ms)、告警一次、并发读者共享一次 in-flight 加载;DB 拷贝改为私有 `mkdtemp` 目录 + 0600,不再落在可预测的 /tmp 路径。
115
+ - **修复(审查周期):其他。** OAuth 回调页对反射的 `error` 参数做 HTML 转义;Windows `openBrowser` 在拼 cmd 字符串前拒绝含引号/换行的 URL;doctor 脱敏覆盖 `1//0…` refresh token、`AIza…` key 与带凭据的代理 URL,config dump 会抹掉 `proxyUrl` 凭据;`/agy pool` 打印脱敏后的代理;fallback 模型目录每分钟最多重探一次(未登录时不再每次列表调用都 spawn `agy models`);会话 id 兜底发现改为监视池账号隔离 HOME;彻底移除 mermaid 的公网 CDN 回退(应用 origin 内的浮动版本无签名脚本),mermaid/reasoning 的 overlay observer 通过 `ctx.effect` 释放。
116
+ - **新增:token 速度统计。** 每条录制事件现在带到达时间;≥500ms 的分步生成窗口给出 `lastTps` / 按输出加权的 `avgTps` / 输出 token 总量,通过 `/agy status`(「last run tokens: N output tokens — avg X tok/s」)与 `lastRun` 状态载荷暴露。
117
+ - **修复(issue #43):桌面端不再看不到思考过程与工具活动。** 两个叠加原因:(1) 思维正文来自 agy 会话 SQLite 库,而读取器依赖外部 `sqlite3` CLI——桌面/GUI 宿主的 PATH 经常没有它(macOS GUI 应用不继承 shell PATH,Windows 从不自带)。读取器现在优先用 Node 内置的 `node:sqlite`(无需外部二进制),CLI 退居兜底,桌面端也能解析出推理文本。(2) 拿不到正文时高强度轮次此前什么都不渲染;现在首段可见文本之前(以及纯思考轮次)会先流出 `[agy thinking turn · N thinking tokens]` 横幅,DB 正文稍后解析出来时无缝追加——不重复横幅、不丢文本,正文已开始后也不在工具密集步骤上刷屏。
118
+ - **修复(issue #45):Windows 号池凭据无法隔离现在是明确、可见的已知限制。** Windows 上 agy 把登录存进系统级 Credential Manager 的单一槽位 `gemini:antigravity`(go-keyring);HOME 隔离只隔离文件,所有池账号实际仍以系统登录认证与扣额度。插件现在 (1) 在 `/plugins/agy-link/status` 暴露 `credentialIsolationLimited` 并在设置面板渲染警告横幅,(2) Windows 下选中隔离账号时告警一次,(3) 在 KNOWN-GAPS.md 记录边界(以及为何 `maxConcurrent > 1` 时逐 run 的 `CredWrite` 槽位互换不安全)。彻底解决需要上游 agy 改动(按 HOME 命名空间的 keyring,或 keyring 槽位为空时回落文件)。
119
+
120
+
121
+ - **修复:卡死 run 的心跳日志不再谎报静默时长。** 当没有未完成的工具步骤时,静默时钟从一个空 map 上的 `Date.now()` 起算,于是每 5 分钟一次的日志永远显示「无新输出 1m」,而 run 实际已经安静了数小时。现在报告的是距最后一条 stdout 行的真实时长(`lastOutputAt`)——这正是用户决定是否按 Stop 时需要的数字。
122
+ - **修复:`agy_tool` 不再把「从未指向过任何步骤的游标」归因为「输出被回收」。** 越界的 step 索引(或非工具事件)与真正被淘汰的录像此前返回同一句「录制输出已不在内存中」——告诉模型输出丢了,而它其实从未被录制过。降级消息现在说明真实原因(「索引在此录像范围之外——尚未录制 / 不是已完成的工具步骤」vs「内存中的录像已被回收」)。
123
+ - **修复:账号故障转移现在真的能续上会话——包括在工具步骤之后(「额度用完、整轮作废」问题)。** DSH 重发失败步骤时,如果失败发生在工具续跑 span 内,消息尾部带着工具结果,于是适配器从**已 settle** 的录像里恢复、把同一个失败原样重放一遍:任何在工具调用之后出现的额度/地区/认证拒绝都会让整轮失败,而旁边还有健康账号闲着(已用 `test/fake-agy.mjs` 离线复现)。现在续跑分支拒绝重放「账号级失败的已 settle 录像」,改为丢弃它并在下一个账号上重新组装——仍在同一步内完成,用户看不到中断。故障转移有界(每会话每 2 分钟最多 `启用账号数 - 1` 次重发),且绝不适用于请求级失败,因此确定性错误不会在账号池里反复重跑用户的工具。
124
+ - **修复:传输形态的「未登录」不再被报成 AUTH(「API 密钥无效」误导)。** agy 对于真正的登出、以及**无法校验** token 的健康登录,都会说「not logged into Antigravity」(线上 2026-10-10 14:17:重试账号的 token 早 9 分钟就过期,刷新死在 `Post https://oauth2.googleapis.com/token: EOF`,同时伴随 TLS 握手超时)。适配器把它归类为 `AUTH`,后果有三:(a) 在健康账号上终止本轮;(b) 把该账号标记为「需重新登录」;(c) 让 DSH 渲染 `message.failure.auth` 文案——「API 密钥无效」——而凭据从来不是问题。现在,只要认证形态的 stdout banner、stderr 或结果信封里出现传输层证据,就会得到可重试的 `NETWORK` 失败:轮换到下一个账号,且绝不隔离任何登录状态。
125
+ - **修复:重试预算跟随账号池,登出也能在另一个账号上继续。** `maxRetries` 原先固定为 1,3 账号池永远到不了第三个账号——而线上事故恰好需要它(主账号 ELIGIBILITY → 第二账号「未登录」→ 整轮丢失,尽管第三个账号健康)。现在预算是 `min(启用账号数 - 1, 2)`;当至少还有一个账号时 `AUTH` 可重试(失败账号被标记 `authRequired` 并退出轮换)。
126
+ - **修复:软容量拒绝改为轮换,而不是直接终止本轮。** 「model overloaded」/「experiencing high traffic」原先归类为确定性 `AGY_ERROR`——终止、不轮换、不重试——于是明明还有别的账号可用,本轮却失败了。现在它们是 `OVERLOADED`:可重试、指针已轮换、且**故意不加冷却**(没有消耗任何额度)。硬额度识别也补上了过去漏掉的真实措辞(`Resource has been exhausted (e.g. check quota).`、`You have exhausted your quota`、`out of quota`、`check quota`)——这些以前会退化成一句「agy exited with code 1」,并且重试仍打在同一个已耗尽账号上。
127
+ - **修复:空闲看门狗不再杀掉「明显还在干活」的 run。** 真实日志一周内 37 次失败里有 12 次正是 `agy run was idle for 600000ms without output`,每次都赔上一轮和一次重发。现在 stderr 也算活跃(agy 会把进度写在 stderr),并且只要 run 产出过任何输出就适用长预算(`longToolIdleMinutes`,默认 240 分钟)——首个事件之前仍用严格的 `timeoutMs`,那才是真正卡死/连不上 Google 的形态。5 分钟心跳也从「仅有未完成工具步骤」扩展为覆盖「安静但存活」的 run,用户能看到它在等,并可以按 Stop。
128
+ - **修复:提示词不再重发当前 agy 会话已有的工具输出,并严格过滤 DSH 合成遥测消息。** 已绑定的会话会把历史工具结果重新塞回 `-p`(线上抓到一条命令行里带着 6.4 KB 的 Godot 模拟输出)——陈旧输出排在真正的问题前面,这正是模型转去回答「上一件事」的原因之一。现在会话已拥有工具输出时不再重发(全新会话仍会带上,因为那时 agy 从未见过)。此外,DSH 在运行时高频注入的 `role: 'user'` 合成遥测消息(如 `time-context` 时间戳、`runtime-context` 上下文快照、`<system-reminder>` 等)现在会被严格过滤,彻底解决时间戳冲掉真实用户任务导致 agy 忽略最新输入的问题;纯图片(无文本)的最新消息也不再回溯拾取旧问题。
129
+ - **修复:被中断的轮次不会再让 agy 回答你打断的那条消息。** DSH 会把 Stop / 插话时的半截回复当作普通 assistant 消息持久化,于是插件把「被打断的用户轮次」当成 agy 原生历史(而 agy 服务端那一轮并没有最终回复)——下一条提示词只有新消息,模型却去回答被打断的那条(线上 `session-c65a826e` 第 17→19 轮,用户原样重问并追问「这个咋不回答呢」)。现在 run 被中断或超时时会在 binding 上标记 `interrupted`,下一条提示词带上明确的「不要继续那件未完成的事」边界,成功一轮后清除该标记。被中断的 run 暂存的图片也不再被记为「已投递」(Stop 之后重发同一张截图会被静默去重,纯图片追问则直接报「request carries no user text or images」)。
130
+ - **修复:已经产出结果的 run 不会再被下一次调用杀掉。** 插话抢占原先会中止一个已经流完答案的进程(记为假的 `ABORTED`,并把 agy 的收尾切断);现在会短暂等待它自然退出,只抢占仍在运行的轮次。有界的 settle 等待在超时时会记录日志,被取代的 run 的迟到回写也无法再回退 `lastMessageCount` / `sentImageIds`。
131
+ - **修复:账号池与会话绑定的健壮性。** 残缺的 `pool.json`(账号缺 `cooldowns`/`quotas`)会让 `selectAccount` 抛 `TypeError`,导致每一轮都以不透明的内部错误失败、且完全没有故障转移——现在加载时会规范化记录。限流不再删除 `(会话, 账号)` 绑定,因此不会为了 15 分钟冷却丢掉一个可续跑的原生会话(只有真正的登出或 agy 侧「conversation not found」才会作废它)。从存活 run 采纳会话 id 时限定为**同一账号**(把账号 A 的 id 用到账号 B 上会产生一次白跑的「conversation not found」)。`sessions.json` 现在真正实现其文档承诺的 dirty-key 合并,web 与 headless 宿主不再互相覆盖绑定。失败的轮次会报告最后一次 per-call 上下文用量,而不是硬编码的 0/0。ADR-012 不再声称「零中断」的平铺回退与 10 分钟冷却。
132
+
133
+ ### English (continued)
134
+
135
+ - **Fix: a long-running span can no longer lose its recording to the shared run registry (`no recorded agy run`).** `RunRegistry` kept only the 8 most recently **created** runs (global FIFO across every DSH session). A run whose span waited hours on a wedged agy tool step (e.g. a headless test scene that hit a script error and never quit) was evicted while it was still in flight, and the `agy_tool` mirror call the span had already emitted — `agy_tool({run, step})` — failed with "no recorded agy run", which sent the model off to debug the bridge instead of finishing the user's task (flash-blast incident, 2026-10-10; the same shape recurred on 2026-10-09). The registry now (1) pins a run for the whole lifetime of the span driving it, (2) exempts pinned and unsettled runs from eviction, (3) never evicts the most recent entry, and (4) `remember()` now refreshes recency (delete-then-set) instead of silently keeping the old slot. Spans re-register their recording when they start and when they finish, so the run an `agy_tool` dispatch addresses is always resident, while memory stays bounded by evicting the oldest settled, unpinned run.
136
+ - **Fix: a missing recording now degrades instead of throwing a harness-internal error at the model.** `agy_tool` returning `no recorded agy run` was unrecoverable for the agent: it read the error as an environment fault and spent a whole turn reverse-engineering the plugin bundle (including a stale published copy from the pnpm store). When a run's recording is gone the mirror now returns an explicit, task-facing notice stating that the step did run, that its replay is unavailable, that there is nothing to debug, and that the operation can simply be re-run — then the agent continues its real work.
137
+ - **Add: configurable long-tool idle budget + stuck-tool heartbeat.** The watchdog raise for open tool steps (previously hard-coded to the ≥ 4 h print-mode ceiling) is now `longToolIdleMinutes` / `DSH_AGY_LONG_TOOL_IDLE_MINUTES` (default 240; `0` disables the raise so a wedged tool fails the turn after `timeoutMs` instead of holding the session for hours). While any tool step is open the adapter now logs a heartbeat every 5 minutes (open count + age + budget), so a stalled turn is visible instead of looking like a dead session.
138
+ - **Fix: `toolStepBrief` reads `CommandLine`.** The code-mode/step preview for `run_command` steps showed "run command" whenever agy sent the modern `CommandLine` key; it now shows `$ <command>` like the native card does.
139
+ - **Fix: cross-conversation & cross-account assistant replies are now digested when returning.** In multi-account pool scenarios where turns alternated between accounts, an assistant message emitted under another agy conversation id was previously ignored by `isForeignAssistant` (because `provider` was identically `antigravity`). The returning conversation never saw that intervening answer in its context digest. `isForeignAssistant` now verifies `source.replayState` conversation IDs against the current binding, guaranteeing complete cross-conversation context.
140
+ - **Fix: `forwardSystemPrompt` falls back to `role: 'system'` messages.** When `options.system` is omitted on `GenerateOptions` but system instructions are provided as an explicit system message in the chat history, the prompt now correctly includes the system instructions.
141
+ - **Add: native card presentations for web search, URL fetching, task management, scheduling, and subagents.** Mirrored cards and step previews now cleanly project `search_web`, `read_url_content` / `web_fetch`, `manage_task`, `schedule`, and `invoke_subagent` / `send_message`, avoiding generic raw JSON fallbacks. `TerminalResultView` also extracts process exit codes (`The command exited with code X`) from `run_command` output to render native status pills in DSH terminal cards.
142
+
143
+ ### 中文(续)
144
+
145
+ - **修复:长时间运行的 span 不会再因为共享 run 注册表而丢失录像(`no recorded agy run`)。** `RunRegistry` 原先只按**创建顺序**保留最近 8 条 run(跨全部 DSH 会话的全局 FIFO)。当某条 run 的 span 因为 agy 工具步骤卡死(例如无头测试场景脚本报错后不退出)等待数小时,它会在仍在飞行时被淘汰,而该 span 已经排好队的 `agy_tool({run, step})` 镜像调用随即报 "no recorded agy run"——模型据此掉头去调试桥接插件,而不是继续用户的任务(flash-blast 事故,2026-10-10;2026-10-09 出现过同样形态)。现在注册表:(1) 在 span 驱动期间**钉住**该 run;(2) 被钉住与未 settle 的 run 免于淘汰;(3) 永不淘汰最近使用的那一条;(4) `remember()` 改为真正的 `delete` 再 `set`,刷新最近使用顺序而不是原地保留旧位置。span 在启动与结束时都会重新登记自己的录像,因此 `agy_tool` 派发时寻址的 run 必定仍在内存中,同时内存仍通过淘汰「最老的、已 settle 且未钉住」的 run 保持有界。
146
+ - **修复:录像缺失时改为优雅降级,而不是向模型抛桥接内部错误。** `agy_tool` 返回 `no recorded agy run` 对 agent 来说不可恢复:它把该错误理解为环境故障,整轮都在逆向插件产物(还是一份 pnpm store 里的旧发布副本)。现在录像不存在时,镜像工具返回一条明确面向任务的提示:该步骤确实执行过、只是回放不可用、这里没有可调试的东西、需要输出就重跑一次同样的操作——随后 agent 继续原本的工作。
147
+ - **新增:可配置的长工具空闲预算 + 卡死工具心跳。** 未完成工具步骤期间的空闲预算(原先硬编码为 ≥ 4 小时的 print-mode 上限)现在是 `longToolIdleMinutes` / `DSH_AGY_LONG_TOOL_IDLE_MINUTES`(默认 240;设为 `0` 表示不抬高预算,卡死的工具会在 `timeoutMs` 后让本轮失败,而不是把会话挂住数小时)。只要存在未完成的工具步骤,适配器每 5 分钟记录一条心跳(未完成数量 + 最久时长 + 当前预算),让卡住的轮次可见,而不是看起来像会话已死。
148
+ - **修复:`toolStepBrief` 会读取 `CommandLine`。** 当 agy 发送新式 `CommandLine` 键时,`run_command` 步骤的 Code Mode / 预览标题只会显示 "run command";现在与原生卡片一致,显示 `$ <命令>`。
149
+ - **修复:跨会话 / 跨账号的助理回复在切回时现在会被完整消化(跨会话上下文丢失漏洞)。** 在多账号池轮换场景下,当某轮切到备用账号并在新 agy 会话中回答后,切回原账号会话时,由于原先 `isForeignAssistant` 仅比对 `provider !== 'antigravity'`,导致另一会话产出的 assistant 回复被误认为“本机历史”而从补充摘要中剔除,使原会话完全缺失该轮的回答上下文。现在通过对比 `source.replayState` 中的会话 ID,跨会话回复会准确作为外部上下文消化到提示词中。
150
+ - **修复:`forwardSystemPrompt` 在 `options.system` 缺省时自动回退读取 `role: 'system'` 消息。** 当宿主未在选项顶层传入 `options.system`、但在消息历史中携带了系统指示时,提示词现在仍能准确拼接系统指示。
151
+ - **新增:全面支持网页搜索、网页抓取、后台任务管理、定时任务及子 Agent 的原生工具卡片渲染。** 镜像卡片与步骤摘要现已原生支持 `search_web`、`read_url_content` / `web_fetch`、`manage_task`、`schedule` 以及 `invoke_subagent` / `send_message`,避免退化为生硬的 raw JSON;终端卡片 `TerminalResultView` 现在会从 `run_command` 输出中提取真实退出码(`The command exited with code X`),以便 DSH 渲染原生状态药丸。
152
+
153
+ ## 0.4.45 (2026-10-10)
154
+
155
+ ### English
156
+
157
+ - **Fix (macOS): `ensureIsolatedKeychain` no longer causes modal SecurityAgent password popups on startup (issue #42).** On macOS, `ensureIsolatedKeychain` applied `security set-keychain-settings` on existing isolated keychains on every boot and spawn. When the keychain was locked across app restarts, macOS's SecurityAgent raised blocking GUI dialogs demanding the password for each account — an unanswerable prompt because the isolated keychain was created with an ephemeral random passphrase. The settings (never auto-lock, no timeout) are already persisted on creation, so `ensureIsolatedKeychain` now returns `false` immediately when the keychain file exists on disk, never poking the security CLI again. New keychains are also provisioned with an empty passphrase (`-p ""`) and unlocked upon creation.
158
+ - **Fix: reasoning thinking thoughts arriving asynchronously are no longer dropped (issue #43).** In `EventMapper`, the thinking announcement gate prematurely consumed `thinkingAnnounced` when `thoughtTokens > 0` even before thought prose had finished resolving from the SQLite database. Later fragments or DONE tails carrying the resolved thought prose were subsequently dropped as "already announced". The announcement gate now only marks thoughts as announced when thought text is actually present. In addition, `readStepThoughts` in the adapter now adds a retry interval to accommodate SQLite WAL flush latencies during active model generation.
159
+ - **Fix (Mermaid): prevent "Syntax error in text" bomb SVG from leaking into the document body.** When a code block containing invalid diagram syntax was processed, Mermaid's fallback error-renderer appended a 2412px-wide SVG with a bomb icon directly into `document.body`. Due to an internal throw before cleanup in Mermaid v11, the leaked elements remained permanently attached to the window footer. `mermaid.initialize` now sets `suppressErrorRendering: true`, pre-validates syntax using `api.parse()`, passes a scoped mount container to `api.render()`, unconditionally sweeps temporary and stray elements (`#dagy-mmd*`, `#iagy-mmd*`) in a `finally` block, and injects protective CSS preventing body-level leaked nodes from rendering.
160
+
161
+ ### 中文 (Chinese)
162
+
163
+ - **修复 (macOS):`ensureIsolatedKeychain` 不再在每次启动时触发 SecurityAgent 钥匙串密码弹窗(issue #42)。** 在 macOS 上,`ensureIsolatedKeychain` 原先在每次 DSH 启动和进程拉起时都会对已存在的隔离钥匙串执行 `security set-keychain-settings`。当钥匙串在应用重启后处于锁定状态时,macOS 的 SecurityAgent 会对每个隔离账号弹出阻塞式系统密码框——由于此前钥匙串是使用未持久化的随机密码创建的,用户输入任何密码都无法通过。钥匙串的“永不自动锁”设置在首次创建时已持久化落盘,因此当钥匙串文件已存在时,`ensureIsolatedKeychain` 现在直接返回 `false`,绝不再调用 security CLI;新建钥匙串时也改用空密码(`-p ""`)创建并立即静默解锁。
164
+ - **修复:异步从数据库解析的思考过程正文不再被静默丢弃(issue #43)。** 在 `EventMapper` 中,思考公布逻辑原先只要检测到 `thoughtTokens > 0`,即使 SQLite 中的思考正文尚未完成异步提取,也会过早消耗 `thinkingAnnounced` 标记,导致后续分片或 DONE 尾包即使成功提取到了思考正文也被判定为“已公布”而直接丢弃。现在公布标记仅在实际存在思考正文时才消耗;同时适配器中的 `readStepThoughts` 增加了重试等待,以包容模型生成期间 SQLite WAL 写入的延迟。
165
+ - **修复 (Mermaid):彻底解决 Mermaid 语法错误导致巨大的“Syntax error in text”炸弹图标残留在窗口底部的问题。** 当代码块包含不合法的图表语法时,Mermaid 内部的兜底错误渲染逻辑会向 `document.body` 追加一个 2412px 宽的巨大炸弹图标(💣)SVG;由于 Mermaid v11 在内部抛错前未执行清理,该临时元素会永久留在窗口底部污染界面。现在初始化时开启了 `suppressErrorRendering: true`,在渲染前通过 `api.parse()` 预校验语法,向 `api.render()` 传入局部挂载容器,在 `finally` 块中无条件清理临时节点(`#dagy-mmd*`、`#iagy-mmd*`),并通过安全 CSS 全面隐藏逃逸到 body 层级的任何错误元素。
166
+
3
167
  ## 0.4.44 (2026-10-09)
4
168
 
5
169
  ### English
@@ -44,6 +208,52 @@
44
208
  - **修复:工具往返续跑不再重复附加全部历史截图。** 当续跑请求里没有新图片时,提示词组装会回退成收集**全部**历史 user 轮次的附件,于是同样的 4~5 张 UI 截图在每一步都被重新落盘、重新引用(实测一个会话 61 个暂存文件只有 5 份唯一内容)。图片转发现在按水位去重(`SessionBinding.sentImageIds`):只暂存当前 agy 会话还没见过的附件。暂存文件名也改为按 attachment id 而不是调用序号,合法重用会覆盖而不是堆积。
45
209
  - **修复:切换账号 / 切换模型后能正确带上此前的对话上下文。** 新建 agy 会话(切账号、切模型、压缩后重播种)此前只用 `digestMaxChars`(8K)做种子摘要,长会话的历史被大量丢掉。首次接入现在使用新的 `contextTransferMaxChars` 预算(默认 660K,面向 1M token 级模型),承载视觉上下文的图片也会通过同一水位机制补发一次。
46
210
 
211
+ ## 0.4.43 (2026-10-03)
212
+
213
+ ### English
214
+
215
+ - **Add: native Mermaid diagram rendering in DSH Desktop and Web.** Antigravity models (especially Gemini / Claude) frequently emit ````mermaid```` code blocks to visualize workflows, timelines, and architectures. Because DSH renders code blocks as plain text, diagrams previously showed as raw source text. `dsh-agy-link` now bundles an offline standalone Mermaid.js runtime (served from `/plugins/agy-link/assets/mermaid.min.js`) and mounts an interactive diagram viewer over `.md-code-block` elements:
216
+ - Non-destructive DOM rendering preserving React's original `<pre>` structure to avoid re-render conflicts.
217
+ - Tab switching between "Diagram" and "Code".
218
+ - Interactive SVG zoom controls (Zoom In, Zoom Out, Reset, Fit).
219
+ - Copy SVG source button with visual feedback.
220
+ - Automatic dark/light theme synchronization with DSH's CSS color variables.
221
+ - **Add: local image & media display (`file:///`, absolute Windows & WSL paths) with fullscreen Lightbox.** Models generating or referencing local visual assets (e.g. concept art, mockups, artifacts) emit `file:///` URLs or local filesystem paths. Because DSH's markdown renderer rejects non-HTTP URLs (`remoteImageUrl`), images previously rendered as inert text (`<span class="imageAlt">`).
222
+ - Added host endpoint `/plugins/agy-link/media` with security validation, MIME type sniffing, stream piping, and client cache headers, supporting Windows drive paths (`D:/...`), UNC paths, and WSL paths (`/mnt/...`, `/home/...`).
223
+ - Integrated live streaming markdown image rewrite (`![alt](file:///...)` -> `![alt](http://127.0.0.1:${port}/plugins/agy-link/media?path=...)`).
224
+ - Retrospective DOM enhancer inspecting React Fiber props on `<span class="imageAlt">` in historical session turns to recover original file paths and restore image previews retroactively.
225
+ - Enhanced local file links (`a[href^="file://"]`) to image files with thumbnail previews.
226
+ - Fullscreen interactive Lightbox modal with zoom, pan, and "Copy Path" button.
227
+ - **Add: interactive user decision & choice adaptation (`ask_question` and Markdown Choices).** Antigravity models actively solicit user decisions and guidance through the `ask_question` tool or structured markdown decision templates (`### 💬 请您拍板` / `请选择`).
228
+ - Extended tool card mirroring for `ask_question` with structured question text, selectable answer badges, and a "Fill into Chat" action button.
229
+ - Extended tool card mirroring for image tools (`generate_image`, `view_file` on images) with inline visual thumbnails and Lightbox triggers.
230
+ - Added markdown choice scanner detecting decision headers and list options, mounting interactive quick-action selection chips that directly fill the selected option text into DSH's chat composer (`[data-composer-input]`).
231
+ - **Fix: code cards referenced a non-existent font token, so the code font silently fell back to the hardcoded stack.** `ROW_CSS` spelled the DSH code-font token as `--dsw-font-family-code`, but the real token is `--ds-font-family-code` (no `w`). The mistyped name has zero definitions anywhere in DSH, so every `var(--dsw-font-family-code, <fallback>)` resolved to its hardcoded fallback (`ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`), which carries no CJK coverage — Chinese text inside agy tool cards rendered in the OS default (SimSun / Microsoft YaHei) instead of the host's configured code font. The three affected selectors are `.agy-tv-card` (whose `font-family` is inherited by the header, copy button and body), `.agy-tv-preview` and `.agy-tv-diff-stat`.
232
+ - **Fix (Windows): auto-detect system proxy from Windows Internet Settings (WinINet) to fix quota refresh and sync timeouts.** On Windows GUI hosts (DSH Desktop / Electron), `process.env` does not inherit terminal `HTTP_PROXY`/`HTTPS_PROXY` variables. Direct requests to Google endpoints (`daily-cloudcode-pa.googleapis.com` / `googleapis.com`) timed out behind GFW, silently dropping quota updates while the frontend showed "额度已刷新". The host now automatically detects system proxy settings from the Windows Registry (`HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings` `ProxyServer`) and routes all undici fetch calls and child process environments through the proxy.
233
+ - **Fix (Windows): Windows Credential Manager multi-account collision / quota draining across pool accounts.** On macOS, each account has an isolated Keychain file (`login.keychain-db`) via `$HOME`. On Windows, `agy.exe` reads credentials via `zalando/go-keyring` (`advapi32!CredRead("gemini:antigravity")`), which accesses Windows Credential Manager globally per logon session and ignores `HOME` / `USERPROFILE`. When secondary pool accounts were spawned, `agy.exe` read the primary account's token from Credential Manager, running secondary turns under the primary account and draining its quota (e.g. `g98` dropping to ~17% while secondary accounts appeared unused). The host now actively synchronizes Windows Credential Manager before each turn with the target account's token, persists any refreshed tokens back to that account's directory upon completion, and restores the primary account upon release.
234
+
235
+ ### 中文 (Chinese)
236
+
237
+ - **新增:DSH 客户端 / Web 端原生 Mermaid 流程图与图表渲染。** Antigravity 上的 Gemini 与 Claude 模型在梳理架构与方案时经常输出 ````mermaid```` 代码块。DSH 原本仅将其作为普通文本代码块展示,导致图表无法直观查看。`dsh-agy-link` 现内置离线 Mermaid.js 运行时(通过 `/plugins/agy-link/assets/mermaid.min.js` 托管),为所有包含 `mermaid` 代码块的区域挂载交互式图表卡片:
238
+ - 无破坏性 DOM 操作,安全保留 React `<pre>` 节点结构以防重渲染崩溃。
239
+ - "图表" 与 "代码" 标签页一键无缝切换。
240
+ - 交互式缩放控制(放大、缩小、重置比例)。
241
+ - 一键复制 SVG 源码到剪贴板。
242
+ - 自动跟随 DSH 主题色(暗色 / 亮色)切换渲染配色。
243
+ - **新增:本地图片与媒体展示(支持 `file:///`、Windows 绝对路径与 WSL 路径)及全屏灯箱。** 模型在生成或引用本地视觉资源(如设计图、原画预览、制品图)时,通常会输出 `file:///` 协议或本地文件系统路径。DSH 内置的 Markdown 渲染器出于安全只接收 HTTP(S) 地址,导致图片回退为静态文本 `<span class="imageAlt">`。
244
+ - 插件后端新增 `/plugins/agy-link/media` 路由,具备安全边界校验、MIME 类型自动推断、流式传输与客户端缓存支持,全面兼容 Windows 盘符路径(`D:/...`)、UNC 路径以及 WSL 环境路径(`/mnt/...`、`/home/...`)。
245
+ - 流式响应实时重写:在模型输出流中即时将 `![alt](file:///...)` 转换为通过本地服务代理的安全可访问 URL。
246
+ - 历史会话无损追溯:前端增强器通过 React Fiber 属性提取历史消息中已失效 `<span class="imageAlt">` 的原始路径,自动补全渲染出真实缩略图,重开历史对话亦可完整查看图片。
247
+ - 本地超链接图片识别:自动识别指向图片的 `file://` 链接并在下方挂载缩略图。
248
+ - 全屏交互式灯箱(Lightbox):支持双击/按钮放大缩小、拖拽平移以及「复制路径」到剪贴板。
249
+ - **新增:用户选择与拍板决策交互适配(`ask_question` 工具卡片与 Markdown 决策选项快捷填入)。** Antigravity 体系下的模型经常调用 `ask_question` 或输出结构化的拍板询问(如 `### 💬 请您拍板:1. 路线 A... 2. 路线 B...`)。
250
+ - 深度适配 `ask_question` 工具卡片:以结构化卡片渲染问题与候选选项,提供候选气泡及「填入输入框」一键操作。
251
+ - 扩展图片类工具卡片预览:`generate_image` 以及针对图片路径调用的 `view_file` 均以大图卡片配合直接查看与灯箱展示。
252
+ - 自动扫描 Markdown 拍板决策区:自动识别「请您拍板 / 请您指示 / 请选择」等段落选项,挂载交互式快捷选项芯片(Chips),点击即可自动填入 DSH 底部聊天输入框,极大提升人机协作效率。
253
+ - **修复:代码卡片引用了一个不存在的字体 token,导致代码字体静默回落到硬编码兜底栈。** `ROW_CSS` 里把 DSH 的代码字体 token 写成了 `--dsw-font-family-code`,而真实 token 是 `--ds-font-family-code`(无 `w`)。这个拼错的名称在整个 DSH 代码库里零定义,因此每一处 `var(--dsw-font-family-code, <兜底>)` 都取到硬编码兜底值 `ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`——该栈不含中文字形,导致 agy 工具卡片里的中文以系统默认字体(宋体 / 微软雅黑)渲染,而非宿主配置的代码字体。受影响的三个选择器是 `.agy-tv-card`(其 `font-family` 会向下继承到标题栏、复制按钮与正文)、`.agy-tv-preview` 与 `.agy-tv-diff-stat`。
254
+ - **修复 (Windows):自动识别 Windows 系统代理(WinINet),修复点击「刷新/同步」额度静默超时失效。** Windows 桌面版客户端(Electron GUI)启动时,`process.env` 默认不包含终端环境下的 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量。当未在设置里显式填入代理时,直连 Google 额度接口(`daily-cloudcode-pa.googleapis.com` 及 `googleapis.com`)会触发连接超时,后端异常捕获后静默退出,前端虽提示「额度已刷新」但数值永远保持原样。插件现增加自动注册表探针(读取 `HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings`),自适应识别系统代理(如 `127.0.0.1:17891`),并自动注入到 undici 请求与子进程环境变量中。
255
+ - **修复 (Windows):Windows 凭据管理器跨账号撞车,导致池中副账号偷偷消耗主账号(g98)额度。** macOS 下每个账号拥有独立的钥匙串文件(`login.keychain-db`),通过重定向 `$HOME` 即可实现天然隔离;而 Windows 下 `agy.exe` 使用 `zalando/go-keyring`,直接调用 Win32 API `advapi32!CredRead("gemini:antigravity")`。Windows 凭据管理器是全局绑定到当前登录会话的,完全忽略 `HOME` 与 `USERPROFILE`!这导致无论池中调度哪个副账号,`agy.exe` 启动时始终读取系统凭据管理器中残留的主账号 token 执行,造成「用着副账号,消耗的全是主账号额度」(导致用户发现 g98 额度无故跌至十几个点而副账号几乎不动)。插件现针对 Windows 实现动态凭据同步:每次调用前将目标账号 token 瞬时写入 `gemini:antigravity`,调用结束后将最新刷新后的 token 存回该账号目录,并自动恢复主账号原始凭据。
256
+
47
257
 
48
258
  ## 0.4.42 (2026-09-30)
49
259
 
package/README.md CHANGED
@@ -135,6 +135,7 @@ dsh plugin --profile web add dsh-agy-link
135
135
  | `defaultEffort` | `DSH_AGY_DEFAULT_EFFORT` | `(模型默认)` | 思考预算:`low` / `medium` / `high` |
136
136
  | `hiddenModels` | `DSH_AGY_HIDDEN_MODELS` | `[]` | 在 `/model` 选择器中隐藏的模型 slug 列表(黑名单语义,新模型默认可见;建议直接在设置面板"模型显示"中点选管理) |
137
137
  | `timeoutMs` | `DSH_AGY_TIMEOUT_MS` | `600000` | 单轮活跃看门狗超时(毫秒) |
138
+ | `longToolIdleMinutes` | `DSH_AGY_LONG_TOOL_IDLE_MINUTES` | `240` | 存在未完成工具步骤(ACTIVE)时的空闲预算(分钟);`0` = 不抬高预算,卡死的工具按 `timeoutMs` 快速失败 |
138
139
  | `workspaceRoot` | `DSH_AGY_WORKSPACE_ROOT` | 会话 cwd | agy 工作区根目录(默认跟随当前会话工作区) |
139
140
 
140
141
  ---