dsh-opencode-go 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -19,7 +19,7 @@ The plugin automatically adds the session headers required by OpenCode Go, reads
19
19
 
20
20
  Supports DSH `0.1.5-rc.1` and later, including alpha, rc, and stable releases. Compatibility will be maintained as new host versions are released.
21
21
 
22
- Verified versions: `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`, `0.1.7-alpha.1`, `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, and `0.2.0-rc.2`.
22
+ Verified versions: `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`, `0.1.7-alpha.1`, `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, `0.2.0-rc.2`, and `0.2.1-alpha.1`.
23
23
 
24
24
  ### Install from DSH (recommended)
25
25
 
@@ -86,7 +86,7 @@ To build from source and install a local package:
86
86
  npm ci
87
87
  npm run compile
88
88
  npm pack
89
- dsh plugin --profile web add ./dsh-opencode-go-0.1.20.tgz
89
+ dsh plugin --profile web add ./dsh-opencode-go-0.1.21.tgz
90
90
  ```
91
91
 
92
92
  Source development builds the plugin explicitly with `npm run compile`; `--legacy-peer-deps` is not required. Multi-version DSH compatibility tests use independent environments and are not installed during ordinary installation or builds. For Headless, replace `web` with `headless`.
@@ -116,9 +116,54 @@ Restart `dsh web` and refresh the browser afterwards. For Headless, replace `web
116
116
 
117
117
  After updating the desktop plugin, fully quit and reopen DeepSeek Harness to load the updated plugin code.
118
118
 
119
+ ## Multiple accounts and switching
120
+
121
+ Open **Settings → OpenCode Go → Accounts** to add up to 20 named API keys. Adding, renaming, replacing a key, removing, and selecting a preferred account take effect immediately. Model capacities and advanced fields still use Save. Existing `apiKeyEnv` configuration appears as the default account without re-entering its key.
122
+
123
+ Each account shows its rolling, weekly, and monthly usage, reset times, and last successful update. Usage refreshes every minute while the settings page is visible. The conversation usage panel also lets you switch accounts; it immediately clears the old account's data and rejects late responses. Temporary failures retain data only for the same account and key.
124
+
125
+ The preferred account is shared by conversations in the current profile and affects subsequent new requests. Requests already generating retain their resolved key. Account metadata and preference survive restart. Web and Headless profiles keep their own configuration. Save or discard a key draft before changing accounts; if another surface switches accounts, the draft remains addressed to the original account.
126
+
127
+ Keys are stored through the host credential service and never returned to the page. Configuration contains account names, IDs, and credential references. Read-only environment keys can be selected but cannot be replaced in the UI. Removing accounts created here also removes their dedicated credential; existing external references are retained. Removing every account withdraws the provider until another account is added.
128
+
129
+ **Automatic fallback** is off by default. When enabled, missing/rejected keys or exhausted quota can trigger attempts with the other accounts in list order, before any content or tool call is emitted and only when no token usage was reported. Each account is tried at most once per request. The preferred account stays selected; a successful fallback is reported in the usage panel with its reason and time. Partial responses, permission errors, ordinary rate limits, network errors, and server errors stay with host recovery. Cancellation stops further attempts. Quotas remain controlled by the upstream subscriptions.
130
+
131
+ For Headless or manual configuration, put the following under the plugin's `config`:
132
+
133
+ ```yaml
134
+ apiKeyEnv: OPENCODE_API_KEY
135
+ accounts:
136
+ - id: primary
137
+ name: Primary
138
+ apiKeyEnv: OPENCODE_API_KEY
139
+ - id: backup
140
+ name: Backup
141
+ apiKeyEnv: OPENCODE_GO_BACKUP_KEY
142
+ autoSwitch: false
143
+ ```
144
+
145
+ Supply both keys through the credential service or environment variables. `apiKeyEnv` selects the preferred account. Omitting `accounts` preserves legacy single-key behavior; `accounts: []` explicitly removes every account.
146
+
147
+ The Accounts card folds: collapsed, it reports the account count, the preferred account, how many accounts are ready, and the one refresh stamp every row shares (the clock time on the day it happened, the full date and time on hover). Expanded, each account takes one row showing its key state, a wide rolling-quota bar (12px, full row width, quarter marks at 25/50/75%, amber from 80% and red once spent), and its reset countdown; the columns are fixed (handle, name, key state, bar, reading, reset, disclosure), and a name too long for its column ellipsises with the full value in its title, so names of different lengths never move the columns after them; the row's chevron opens the weekly and monthly windows and the rename, replace-key, and remove actions. Drag a row's handle — or focus it and press ↑ / ↓ — to reorder. The list reads top to bottom: the first row is the preferred account and becomes preferred on drop, and automatic fallback follows the same order.
148
+
149
+ ## Network proxy
150
+
151
+ Open **Settings → OpenCode Go → Advanced settings → Proxy address**, enter a proxy URL and click **Save**. For example:
152
+
153
+ ```text
154
+ http://127.0.0.1:7890
155
+ socks5://127.0.0.1:1080
156
+ ```
157
+
158
+ HTTPS proxies and authenticated URLs such as `http://username:password@host:port` or `socks5://username:password@host:port` are also supported. URL-encode special characters in the username and password.
159
+
160
+ The next conversation, model listing, metadata download and usage query use the saved proxy without a restart. Save a blank address to use the default network settings; **Reset** restores the inherited configuration value. A failed proxy connection reports an error rather than falling back to a direct connection.
161
+
162
+ Connections originate from the machine running DSH. On a remote deployment, `127.0.0.1` refers to that server. Headless profiles can set `proxyURL` in the plugin configuration as well.
163
+
119
164
  ## Subscription usage display
120
165
 
121
- Under **Settings → OpenCode Go → Advanced settings → Usage display**, choose a mode and save: **Auto (default)** shows the pill only for this plugin's DSH OpenCode Go models; **Always** keeps it visible with other models, including the built-in `opencode-go` provider; **Off** hides it and stops polling. Disabling OpenCode Go hides the pill in every mode. Click the pill to open the usage panel.
166
+ Under **Settings → OpenCode Go → Advanced settings → Usage display**, choose a mode and save: **Auto (default)** shows the pill only for this plugin's DSH OpenCode Go models; **Always** keeps it visible with other models, including the built-in `opencode-go` provider; **Off** hides it and stops polling. Disabling OpenCode Go hides the pill in every mode. Click the pill to open the usage panel: its first row is the account in use (state dot, name, Preferred tag, Switch), and Switch turns the card's body into the account list — the current account ticked, reachable with ↑ / ↓ or a click, Escape stepping back. That choice decides which account spends now; the consumption order stays the one this page sets. The three windows take one row each (label, bar, reading) with the reset stamp beneath, and the refresh stamp sits in the card's foot.
122
167
 
123
168
  Usage refreshes every minute. Temporary network or service errors retain the last reading for the same account, with a failure notice, timestamp, and reason; the usage panel offers an immediate retry. Initial and authentication failures do not show old usage. Catalog, metadata, and usage JSON requests retry a transient connection reset once within the original timeout budget; this cannot guarantee recovery while the network is failing.
124
169
 
package/README.md CHANGED
@@ -13,13 +13,14 @@
13
13
  - **图片输入**:支持目录中声明图片能力的模型。
14
14
  - **模型容量覆盖**:可按模型覆盖上下文窗口和最大输出,空值继承在线目录。
15
15
  - **逐模型开关**:每个模型独立控制会话中的显示状态,修改立即生效;普通模型默认开启,过时模型默认关闭,也可单独开启。
16
+ - **网络代理**:在设置页配置 HTTP(S) 或 SOCKS5 代理,统一用于对话、模型目录更新与订阅用量查询。
16
17
  - **提示与缓存**:插件不增加隐藏系统提示;会话 ID 用于网关路由。
17
18
 
18
19
  ## 安装与使用
19
20
 
20
21
  支持 DSH `0.1.5-rc.1` 及以上版本,包括 alpha、rc 和正式版,并持续跟随宿主版本更新维护兼容性。
21
22
 
22
- 已验证版本:`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2` 和 `0.2.0-rc.2`。
23
+ 已验证版本:`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.2` 和 `0.2.1-alpha.1`。
23
24
 
24
25
  ### 在 DSH 中安装(推荐)
25
26
 
@@ -29,7 +30,7 @@
29
30
 
30
31
  ![在 DSH 插件页添加、安装并启用 dsh-opencode-go](docs/assets/install-via-dsh.gif)
31
32
 
32
- 然后打开 **设置 → OpenCode Go**,填入 API Key 并保存,即可在会话中选择 OpenCode Go 模型。
33
+ 然后打开 **设置 → OpenCode Go**,填入 API Key 并保存,或在「账号」中添加账号,即可在会话中选择 OpenCode Go 模型。
33
34
 
34
35
  也支持在添加插件时直接输入 Git 仓库地址:
35
36
 
@@ -86,7 +87,7 @@ dsh --profile headless --patch ./headless.patch.yml "你好"
86
87
  npm ci
87
88
  npm run compile
88
89
  npm pack
89
- dsh plugin --profile web add ./dsh-opencode-go-0.1.20.tgz
90
+ dsh plugin --profile web add ./dsh-opencode-go-0.1.21.tgz
90
91
  ```
91
92
 
92
93
  源码开发时显式运行 `npm run compile`;普通用户直接安装预编译产物,无需 `--legacy-peer-deps`。多版本 DSH 兼容性测试使用独立环境,不参与普通安装或构建。Headless 用户将 `web` 换成 `headless`。
@@ -116,9 +117,44 @@ dsh plugin --profile web update dsh-opencode-go --latest
116
117
 
117
118
  桌面版升级后,请完全退出并重新打开 DeepSeek Harness,以加载更新后的插件代码。
118
119
 
120
+
121
+
122
+ Headless 或手动配置可在插件的 `config` 中填写:
123
+
124
+ ```yaml
125
+ apiKeyEnv: OPENCODE_API_KEY
126
+ accounts:
127
+ - id: primary
128
+ name: 主账号
129
+ apiKeyEnv: OPENCODE_API_KEY
130
+ - id: backup
131
+ name: 备用账号
132
+ apiKeyEnv: OPENCODE_GO_BACKUP_KEY
133
+ autoSwitch: false
134
+ ```
135
+
136
+ 分别通过凭据服务或环境变量提供两个引用的 Key。`apiKeyEnv` 指向首选账号;省略 `accounts` 保留旧版单 Key 行为,`accounts: []` 表示已移除全部账号。
137
+
138
+ 「账号」卡片默认可收起:折叠时显示账号数量、首选账号、可用数量与统一的刷新时间(当天只显示时刻,悬停给出完整日期时间)。展开后每个账号占一行,行内显示 Key 状态、加粗的 5 小时额度条(12px 通栏,25%/50%/75% 刻度,≥80% 转琥珀、满额转红)与重置倒计时;各列位置固定(手柄 / 名称 / Key 状态 / 额度条 / 百分比 / 重置倒计时 / 详情箭头),账号名过长时省略并悬停显示全名,因此不同长度的账号名不会挪动后面的列;点开行尾箭头可查看周/月额度,以及重命名、替换 Key、删除操作。按住行首手柄上下拖动(或聚焦手柄后按 ↑ / ↓)即可调整顺序,**从上到下依次尝试**:第一行即首选账号,松手后它同时成为新的首选;自动回退开启时按同一顺序尝试其他账号。
139
+
140
+ ## 网络代理
141
+
142
+ 打开 **设置 → OpenCode Go → 高级设置 → 代理地址**,填写代理 URL 后点击 **保存**。例如:
143
+
144
+ ```text
145
+ http://127.0.0.1:7890
146
+ socks5://127.0.0.1:1080
147
+ ```
148
+
149
+ 也支持 HTTPS 代理,以及 `http://用户名:密码@主机:端口`、`socks5://用户名:密码@主机:端口` 形式的认证代理;用户名和密码中的特殊字符需要 URL 编码。
150
+
151
+ 保存后,后续对话、模型列表、模型元数据下载和用量查询立即使用新代理,无需重启。清空地址并保存使用默认网络设置;「重置」恢复配置中继承的值。代理连接失败会报告错误,不会自动改为直连。
152
+
153
+ 代理连接由运行 DSH 的机器发起。若 DSH 部署在服务器上,`127.0.0.1` 指服务器自身。无头模式也可以在插件配置中设置 `proxyURL`。
154
+
119
155
  ## 订阅用量显示
120
156
 
121
- 在「设置 → OpenCode Go → 高级设置 → 额度显示」中选择并保存:**自动(默认)**仅在选中本插件的 DSH OpenCode Go 模型时显示;**常驻**在使用其他模型(包括宿主自带的 `opencode-go`)时也显示;**关闭**隐藏胶囊并停止轮询。停用 OpenCode Go 后所有模式均隐藏。显示时点击胶囊展开用量面板。
157
+ 在「设置 → OpenCode Go → 高级设置 → 额度显示」中选择并保存:**自动(默认)**仅在选中本插件的 DSH OpenCode Go 模型时显示;**常驻**在使用其他模型(包括宿主自带的 `opencode-go`)时也显示;**关闭**隐藏胶囊并停止轮询。停用 OpenCode Go 后所有模式均隐藏。显示时点击胶囊展开用量面板:首行是当前使用的账号(状态点 + 名称 + 首选标签 + 「切换」),点「切换」后卡片内容变为账号列表(当前账号带勾,↑ / ↓ 或点击选择,Esc 返回额度视图);这里切换的是"现在用哪个账号的额度",额度消耗顺序仍由设置页那列顺序决定。三个窗口各占一行(名称、进度条、百分比),重置时间在下方,刷新时间在卡片底部。
122
158
 
123
159
  用量每分钟刷新。临时网络或服务错误会保留同一账号的上次数据,并标明刷新失败、更新时间和错误原因;可在用量弹层中立即重试。首次获取失败或鉴权失败时不显示旧额度。模型目录、模型配置和用量的 JSON 请求遇到短暂连接重置时会在原有超时范围内额外重试一次;这不能保证故障中的网络恢复可用。
124
160
 
@@ -12,7 +12,7 @@ The root package contains one coherent DSH 0.1.6-alpha.1 development environment
12
12
  | `npm run test:ci` | Verify shipped artifacts match source, then run the core suite |
13
13
  | `npm test` | Rebuild, then run the core Vitest suite |
14
14
  | `npm run test:compat` | Pack once and test the installed artifact across every supported host fixture |
15
- | `npm run test:compat -- v015-rc1 v017-rc2 v020-rc2` | Test only the named fixtures; unknown names fail |
15
+ | `npm run test:compat -- v015-rc1 v017-rc2 v021-alpha1` | Test only the named fixtures; unknown names fail |
16
16
  | `npm run test:install` | Test npm and pnpm 11.7.0 Git installs of prebuilt artifacts without plugin build approval |
17
17
  | `npm run test:install -- npm` / `-- pnpm` | Run just one package manager; unknown names fail |
18
18
  | `npm run verify` | Run core, compatibility, and Git-installation checks |
@@ -29,7 +29,7 @@ The Git-installation regression creates a fresh repository containing the shippe
29
29
 
30
30
  ## Installed-package matrix
31
31
 
32
- `scripts/compatibility-hosts.mjs` lists nine host generations, including DSH `0.2.0-rc.2`. Each `tests/hosts/<id>` has a private manifest and its own lockfile. DSH packages and their required DSH peer/dependency closure are pinned to that host generation, together with compatible Cordis packages. This prevents a broad upstream peer range from silently selecting a later host generation. The 0.2 fixture also installs its real `dsh-llm-pi-ai` adapter and public pi-ai dependency alongside this plugin.
32
+ `scripts/compatibility-hosts.mjs` lists ten host generations, including DSH `0.2.0-rc.2` and `0.2.1-alpha.1`. Each `tests/hosts/<id>` has a private manifest and its own lockfile. DSH packages and their required DSH peer/dependency closure are pinned to that host generation, together with compatible Cordis packages. This prevents a broad upstream peer range from silently selecting a later host generation. The 0.2 fixtures also install their real `dsh-llm-pi-ai` adapter and public pi-ai dependency alongside this plugin.
33
33
 
34
34
  The runner performs these steps for each host:
35
35
 
@@ -53,6 +53,8 @@ This upgrade does not enable developer messages, tool-change blocks, or deferred
53
53
 
54
54
  There is one explicit npm prerelease exception: DSH evaluates plugin ranges with prereleases included, whereas npm does not generally accept a future prerelease under `>=0.1.5-rc.1`. Only the tarball-install step uses `--legacy-peer-deps` after the host has passed strict installation. This is not a project `.npmrc` policy and does not affect root installs. The post-install version assertions ensure npm did not replace pinned host packages. Changing the plugin's published version policy is a separate decision.
55
55
 
56
+ DSH 0.2.1-alpha.1 uses Cordis 4.0.5-alpha.1 and Schemastery 3.18.5-alpha.1 and no longer publishes runtime invariant plugins. Its fixture pins that dependency generation without the removed package. The plugin does not import invariant entrypoints or replace the former composer statistics row; its quota pill uses the retained `conversation.input.right` list slot. The installed client check verifies that the published composer still declares that slot, and profile checks validate proxy writes, rejection, clearing and reset through each modern host's real settings service.
57
+
56
58
  ## Updating a fixture
57
59
 
58
60
  Add or update the entry in `scripts/compatibility-hosts.mjs`, its private manifest, and its lockfile together. Pin each DSH package reached through dependencies and required peers to the intended host generation; do not upgrade an old host merely to satisfy a newer package. Keep compatible Cordis versions and the client fixture's React, DOM, CSS, and UI dependencies explicit.
@@ -1,5 +1,50 @@
1
1
  # Verification
2
2
 
3
+ ## Release 0.1.21: include the frosted usage panel (2026-10-07)
4
+
5
+ The release combines the network proxy settings and DSH 0.2.1-alpha.1 compatibility with PR #39's dedicated usage-panel material and inner scroll container. The release includes the final merged implementation of both changes.
6
+
7
+ An independent PR checkout passed strict `npm ci`, **487 tests in 32 files**, and freshness checks for **39 shipped artifacts** on macOS / Node 24.14.1. A direct Lightning CSS probe reproduced the declaration merge: placing the prefixed spelling last emitted only that spelling; an unprefixed declaration retained `backdrop-filter`. The PR's [13 GitHub checks](https://github.com/Duskriver/dsh-opencode-go/actions/runs/37408981047) and the [merged main checks](https://github.com/Duskriver/dsh-opencode-go/actions/runs/37493340359) passed, including the ten installed-host generations and npm/pnpm Git installs. The complete local verification passed 487 tests, 39 shipped artifacts, all ten installed-host generations and both npm/pnpm Git installation checks.
8
+
9
+ ## Usage panel frosted glass: the build dropped the working blur (2026-10-06)
10
+
11
+ The panel's frosted attempts shipped no blur at all, and the hunt misread why. `scripts/build.mjs` minifies every CSS module with lightningcss, which treats `backdrop-filter` and `-webkit-backdrop-filter` as one logical property and ships only the LAST spelling in the file. The source listed the prefixed spelling last, so `lib/client.js` carried only `-webkit-backdrop-filter` — and current Chromium ignores that alias entirely. Replayed over identical text in headless Edge (standard deviation of the panel interior; **58.06** is the unblurred baseline): the prefix-only rule measured **58.06** and produced a byte-identical screenshot to no filter at all, the unprefixed rule measured **1.60**, and the shipped `var(--dsw-menu-backdrop-filter, blur(40px) saturate(150%))` form measured **1.60** with the same bytes. The failure was silent in the other direction too: with only the prefixed declaration left, `@supports not (backdrop-filter: blur(1px))` still evaluates as supported, so the opaque fallback never engaged — the panel stayed translucent over unblurred chat, which is exactly what every earlier attempt looked like. The structural theories chased around it (composer ancestors as backdrop roots, element-level `isolation`, portaling to `document.body`) were orthogonal: lightningcss stripped the blur from every build, including the portal build whose marker was proven live. The plugin the report compared against blurs because its stylesheet is a raw string that never passes through lightningcss.
12
+
13
+ The fix paints the material the way the host's own `MenuSurface` does: a dedicated `.material` child (absolute, `inset: 0`, `z-index: -1`, `pointer-events: none`) carries `var(--dsw-menu-surface-fill)` under `var(--dsw-menu-backdrop-filter)`; the panel keeps `isolation: isolate` so the negative-index layer cannot slip behind the composer card; scrolling moved from the panel to an inner `.body`, so the material never rides a scroll container; the hairline became the host's elevation stroke. Only the unprefixed `backdrop-filter` appears in the source, with a comment naming the trap, and `tests/usage-pill-material.spec.ts` pins all three invariants: no prefixed spelling in the stylesheet, the blur on `.material` rather than on the panel or its scroller, and the `@supports` arm that falls back to an opaque layer-2 surface.
14
+
15
+ Validation: `npm run typecheck` and the production build passed; `npm run test:ci` passed **487 tests in 32 files** and the byte-for-byte freshness check for all **39 shipped artifacts** on Windows / Node 24.15.0. `DSH_COMPAT_CONCURRENCY=4 npm run test:compat` passed **all ten installed-host generations** in **256 seconds** including cleanup once the local per-fixture ceiling was raised to 180 seconds: the client fixture needs **30.3–31.6 seconds** here, just over the committed 30-second limit, which is why the default ceiling SIGTERMs it on this machine. The built stylesheet was replayed in headless Edge over a dense text wall (interior std **1.60** against a **58.06** text baseline), and an A/B twin with the filter disabled kept the ghost text. The same stylesheet and panel markup were installed in a Desktop host before this branch, where the blur appeared after restarting the Host; the merged build was not re-installed here, and the Host serves plugin code only at startup.
16
+
17
+ ## Network proxy validation and DSH 0.2.1-alpha.1 (2026-10-06)
18
+
19
+ Settings now accept HTTP, HTTPS and SOCKS5 proxy URLs, including URL-encoded authentication. The per-mount Undici transport routes model inference, gateway model discovery, models.dev metadata and account usage through the saved proxy. Blank explicitly disables an inherited proxy; Reset removes the override. Settings changes apply to later requests while active response bodies finish on their original connection, and proxy failure never falls back to a direct connection.
20
+
21
+ DSH 0.2.1-alpha.1 has an independent installed-host fixture with its complete published DSH dependency generation, Cordis 4.0.5-alpha.1 and Schemastery 3.18.5-alpha.1. It omits the removed invariant package. The plugin does not use invariant exports or replace the retired composer statistics row; the quota pill's `conversation.input.right` slot remains declared in the published composer. Client fixtures now verify that declaration and the expanded proxy field. Real profile fixtures exercise saving all three proxy schemes, rejecting invalid values, clearing and resetting without remounting the plugin.
22
+
23
+ On macOS / Node 24.14.1, `DSH_COMPAT_CONCURRENCY=4 npm run verify` passed **484 tests in 31 files**, freshness checks for **39 shipped artifacts**, **all ten installed-host generations** (38.2 seconds including cleanup), and both **npm and pnpm Git installation checks** (9.4 seconds). Proxy regressions use authenticated loopback HTTP CONNECT and SOCKS5 servers across all three model protocols, verify metadata and usage routing, preserve HTTPS certificate and hostname validation, and cover failed connections, captured requests, live changes and cancellation. Clean copies also passed strict `npm ci` and the core checks with Node 22.19.0 / bundled npm 10.9.3 and Node 24.14.1 / bundled npm 11.11.0. Automated inference uses local fixtures. The earlier Desktop installation loaded the proxy input on DSH 0.2.0-rc.2; latest-host validation uses an isolated installed consumer.
24
+
25
+ ## Account card owns credential editing (2026-10-06)
26
+
27
+ The settings page no longer shows the standalone preferred-account Key field when account management is available. The first card contains provider activation; adding or replacing credentials happens in the account card. Callers without account actions retain the standalone credential control. The production build, Host/Client type checks, all **466 tests in 30 files**, and the freshness check for **37 shipped artifacts** passed. Existing model-switch and locale-change draft checks now exercise the staged refresh interval, while account credential behavior remains covered by the account suite. The user tested the installed Desktop package and accepted the changes before requesting the merge.
28
+
29
+ ## PR #38 account regressions (2026-10-06)
30
+
31
+ Adding an account now persists a configured legacy account, or one whose credential status is unknown, so selecting the new account cannot remove the old one from the list or fallback candidates. Only a confirmed empty legacy placeholder is discarded. Removing a managed account now retains its metadata and key when credential description fails or omits that reference, allowing the same operation to be retried. Gateway key rejections are cached by normalized endpoint and credential reference; switching endpoints rechecks the preferred account, returning to an endpoint retains its own rejection, and replacing a credential clears its rejections across endpoints.
32
+
33
+ On macOS / Node 24.14.1, the production build passed both Host and Client type checks. `npm run test:ci` passed **466 tests in 30 files** and the byte-for-byte freshness check for all **37 shipped artifacts**. Seven added regressions cover legacy default/custom references, rejected/thrown/missing credential descriptions with successful retries, endpoint isolation, equivalent trailing-slash URLs, and credential invalidation across endpoints; the existing unknown-credential test now verifies preservation through repeated additions and selection changes. `npm run test:compat -- v020-rc2` passed the installed Host, transcript, reasoning, client, profile and bundle fixtures for **DSH 0.2.0-rc.2** in **63.5 seconds** including cleanup. Automated checks used local gateway fixtures; the user subsequently accepted the installed Desktop package.
34
+
35
+ ## Usage pill collapses under a narrow composer instead of wrapping or overflowing
36
+
37
+ The account usage pill in the composer's trailing control group rendered its whole label — brand prefix, both window readings with unit words, the optional stale suffix, and (with more than one account configured) the selected account name — as one unbreakable string. That group is `flex: none` by design, so below the Host's own ~460px composer-row breakpoint it already wraps onto its own line; the pill's own untruncated width then forced a *second* wrap inside that line. Narrower still, the pill's width exceeded the row's remaining content box and pushed the send/model controls past the composer card's right edge. The multi-account work made the worst case worse: the account name is the longest of the four segments and appears exactly where space is scarcest.
38
+
39
+ The fix keeps the label as one string for accessibility — `aria-label` and the newly added `title` carry the full text, and `textContent` still concatenates to exactly the old string — but renders it as addressable `<span>`s (a `brand` prefix, a `reading` group with a `data-stale` flag, `unit` words inside the reading, a `stale` suffix, and an `account` segment). The account segment is capped at `12ch` with an ellipsis to keep valid names of up to 80 characters from making the pill arbitrarily wide. Two anonymous `@container` tiers use the same composer row already used by `PermissionSelect.module.css`:
40
+
41
+ * **≤ 460px** — hide the `brand` prefix, `stale` suffix and account name. A bare `::after` asterisk on `[data-stale]` replaces the stale wording, so the signal survives without adding text. The popup's own account selector restates the account name in full.
42
+ * **≤ 360px** — additionally hide the `unit` words, leaving only the bare percentages.
43
+
44
+ Both tiers are CSS-only (`display: none`), so `textContent` — and every existing assertion built on it — is unaffected; jsdom does not evaluate container queries, so the unit tests exercise the unsegmented string in both states. The non-usage state (`···` / unavailable) gets the same treatment for the account segment and keeps the brand span, so a failure while a second account is selected does not reintroduce the overflow.
45
+
46
+ Validation: `npm ci --strict-peer-deps`, both TypeScript faces, and the production build passed on Linux / Node 22.23.3 / npm 10.9.9. `npm run test:ci` passed **431 tests in 30 files** and the freshness check for all **37 shipped build files**, including twelve added segment assertions in `tests/usage-pill.client.spec.tsx` (the `brand`/`unit`/`reading`/`stale` shape, the `title` attribute, the unsegmented non-usage state) and three new cases for the account segment — present and hideable with more than one account, present in the non-usage state, and absent with at most one. One pre-existing `tests/localization.client.spec.tsx` assertion moved from `getByText` to the trigger's `textContent`, because the label is no longer a single text node. `DSH_COMPAT_CONCURRENCY=4 npm run test:compat` passed **all nine installed-host generations** in **46.9 seconds** including cleanup, and `npm run test:install` passed for both **npm (4.7 s)** and **pnpm (2.5 s)** Git installations, loading the shipped artifacts without source builds or plugin build approval. Every check used loopback fixtures; the 460px and 360px cut points were not re-measured in a browser on this branch, and no installed user profile, GitHub issue or npm publication was changed.
47
+
3
48
  ## Release 0.1.20: usage pill and settings draft fixes (2026-10-02)
4
49
 
5
50
  The release includes the Host scope receiver fix and shared no-op draft predicate from PR #34. The installed-client compatibility fixture now reads instance state and exercises both SSR snapshot reads and client subscription cleanup. Desktop follow-up confirmed that saving Always works after restarting a Host process that predated the installed plugin update; the upgrade instructions now explicitly require quitting and reopening desktop Harness.
@@ -517,3 +562,45 @@ Geometry was measured in headless Microsoft Edge 154 (`--headless --remote-debug
517
562
  The stylesheet contract moved with the client fixture: `tests/fixtures/client-compatibility.mjs` now holds the distributed CSS to the whole-row hover rule and the 45 : 55 split, replacing the assertions that lived in the removed `tests/artifact.client.spec.ts`. The component test pins the model id and release date to one line with the date's provenance in its title.
518
563
 
519
564
  One operational note came out of the installed-profile follow-up: a desktop Harness loads plugin code into its host process at startup, so installing a new package, reloading the browser, or reinstalling the plugin never reloads it. A build predating `inputModalities` therefore kept serving release dates and deprecation flags while every model still read "not declared", until Harness itself was restarted. Model membership, capacities, lifecycle data and modalities all ride that one process.
565
+
566
+ ## Multi-account hardening: add/remove transactions, fallback notice, rejected-key cache (2026-10-05)
567
+
568
+ An adversarial review of the multi-account chain (dd0e4b4 plus the fold/reorder branch) confirmed the storage, fallback, and credential-isolation design, and found four client-side defects plus two host-side gaps. Every defect was reproduced by a regression written before its fix.
569
+
570
+ Adding the first account while the page's credential describe had not answered — or kept failing — materialized the legacy placeholder row into the stored accounts and left the new account unselected. The add now asks the credential service directly for any reference the loaded rows do not know: the legacy placeholder is never written (it stays a read-side view while the selection still names the legacy reference), and the new account becomes preferred only when every listed account is known to hold no key; an unanswered describe keeps the selection conservative.
571
+
572
+ Removing an account deleted its settings entry before its stored key, so any unset failure stranded an orphan plaintext credential with no retry path. The remove now unsets first (after a describe confirms the reference is configured) and keeps the row on refusal behind a new "key could not be deleted" notice; a refused settings write leaves a keyless, visible, retryable row instead of a dangling secret. An add whose revision was moved by a concurrent writer re-reads and re-applies the same account (same credential reference, up to three attempts) instead of stranding the key it just stored. A row editor whose row left the list (remove accepted, key cleanup failed) used to hide its own Cancel together with the Add button; the card now drops the stranded editor as soon as its row disappears.
573
+
574
+ On the Host side, the fallback notice's fromRef was pinned to the preferred reference rather than the first account that actually failed (visible with three or more accounts); it now names the real source, or the first reference skipped for a remembered rejection. A gateway-rejected key (401/invalid-key) is remembered for five minutes and skipped by later requests instead of re-sending a full transcript to a dead key; a locally missing credential is not remembered (it is re-checked for free), an all-rejected list still attempts in order so the gateway's own error surfaces, and any `credentials/reference-updated` event clears the entry. The collapsed usage pill now carries a fallback mark (and a bare `↳` marker under the 460px container tier) whenever a switch notice is active; a failed describe marks rows "Status unknown" instead of an eternal loading state; renaming an account no longer refetches every account's quota.
575
+
576
+ Validation: `npm test` rebuilt both artifacts and passed **450 tests in 30 files** with both TypeScript checks, including regressions for each fix: the placeholder race with and without a pending describe, the conservative unknown state, the conflict retry reusing one credential, key-first removal and its retry, stranded-editor recovery, multi-hop notice attribution, rejected-key skipping and credential-change clearing, and the collapsed-pill mark. These checks use local fixtures and mock gateways; no paid inference, installed-profile update, or package publication was performed. Known limits carried forward: concurrent settings writes from two Host processes can still lose updates inside the host watcher's observation window (a dsh-settings-level boundary), and the anthropic-messages error-body classification remains dormant until such models appear in the catalog.
577
+
578
+ ## Accounts card: reorder-only rows, one refresh stamp, a wide quota bar (2026-10-05)
579
+
580
+ Three pieces of the card read as noise or as too small, and a rendered preset comparison (`mockups/row-quota/overview.png`, `presets.png`) settled the shape before any code moved. The two hint paragraphs under the card header are gone, and the per-row "Move up" / "Move down" buttons went with them: the drag handle already reorders the list. The deleted paragraph was the only place that explained the drag, so the handle now carries that sentence as its `title` as well as its accessible name, while ↑ / ↓ keeps the keyboard path; a regression test pins the tooltip.
581
+
582
+ The refresh stamp left every row's detail and moved into the card header. One pass reads every account, so the card was repeating N identical lines; it now renders one stamp, visible while the card is folded, showing the clock time on the day it happened and the date otherwise, with the complete local timestamp in its `title`. The detail keeps the weekly and monthly windows and the three row actions.
583
+
584
+ The rolling reading is now a 12px bar that fills the row's free width (about 330px where the old one was a fixed 132px) with quarter marks at 25 / 50 / 75%, its percentage moved to the bar's right in the card's own 12.5px semibold tabular figure. Colour follows the composer pill's rule — green below 80%, amber from 80%, red at 100% or rate-limited — through one `barLevel()` helper, and the opened windows keep the native `<progress>` element while gaining those tiers through `accent-color`, so a collapsed row and its own detail can never disagree. The bar keeps `role="progressbar"` with `aria-valuenow`, so replacing the native meter cost the row no semantics; row height is unchanged, because the taller bar sits inside the row's existing padding.
585
+
586
+ The row's summary line was content-driven: the name and key-state fields took their intrinsic widths, so two accounts whose names differ in length put their key state, their bar and their reading at different x positions. Measured in headless Edge at the settings column's 858px card width, the key-state column and the bar start moved by **112.1px** across four names, and the bar itself ranged from 181.3 to 237.3px. `Section.module.css` now lays the line out as a grid of fixed columns — 16px handle, 112px name, 92px key state, the bar's `minmax(0, 1fr)` track, 38px reading, 106px countdown, 14px disclosure — so only the bar absorbs slack. The flexible spacer is gone, the name column ellipsises with the full value in its `title`, and a row with nothing to read yet spans the bar, the reading and the countdown with one message instead of adding a column. The same measurement over the same four names now reports a **0px spread** for the key state, the bar, the reading, the countdown and the disclosure, with an identical 372px bar in every row.
587
+
588
+ Validation: `npm run compile` regenerated both artifacts and `npm run test:ci` passed with a byte-identical `check:dist` — **455 tests in 30 files** — with `tsc -p tsconfig.host.json` and `tsc -p tsconfig.client.json` clean. Five new client regressions cover the row's fixed columns (seven children in order, the bar's role there, the ellipsised name's `title`, and the same grid for a row with nothing to read yet), the single header stamp surviving both the unfolded card and every row opened, the row's actions being exactly rename / replace-key / remove with no move control left, the bar's `aria-valuenow` plus its three quarter marks and fill width, and the 79 / 80 / rate-limited level boundaries. One unrelated timing test (`tests/metadata-transport.spec.ts`, a bandwidth-limited catalog deadline) flaked once under full-suite load, then passed alone and in the repeat run.
589
+
590
+ The pull request's own CI then failed the installed-host matrix, and the failure was a client-side injection defect introduced by the first fix for the rows' quota reads. Naming `remote.opencodeGoUsage` in both settings injections is inert on a real host — the two lists are generation-exclusive, so only the applicable one fires — but `tests/fixtures/client-compatibility.mjs` fires an inject callback when `configForms`, `settingsScope` or `remote.opencodeGoUsage` appears in the service list, so both lists matched and the section mounted twice: all nine hosts failed `client-compatibility.mjs` with `1 !== 0` at the `bindScope` / `getForm` call-count assertion. The defect reproduced locally before the fix (`DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: `AssertionError … at client-compatibility.mjs:110`, every host failing) and the same run passes after it (`PASS: installed-package compatibility across 9 host(s)`, 283.3s). The rows now leave upstream's two injection lists untouched and wait on the promise that publishes the namespace — `$mount(goRemote)`, the guard the catalog read already used — before reading a quota; that is the same context and the same call upstream's own default `readUsage` makes, with only the wait added.
591
+
592
+ The rows' quota reads then had to be re-plumbed, and both rejected shapes are worth recording because each was wrong in a different direction. A read needs a context that injected the plugin's own usage namespace: the shipped client answers `cannot get property "remote.opencodeGoUsage" without inject` when one is missing, and that is exactly what a desktop install showed — both rows reporting that sentence behind a retry button — after the namespace was dropped from the settings injections and the read merely waited on the mount. Naming it in both settings injections instead mounts the page twice wherever a host carries both service sets, which is what the installed-host matrix rejected. Waiting on `$mount(goRemote)` alone publishes the namespace but grants no context the right to open it, so it reproduces the runtime error rather than the double mount.
593
+
594
+ The shape that satisfies both is one injection of its own, the way the pill declares the namespace it reads: `mountSettings` captures a bound reader from `ctx.inject(['remote.opencodeGoUsage'], ready => …)`, the two generation-specific settings injections keep exactly the services upstream names, and a session that never injects the namespace reports "OpenCode Go account usage is unavailable in this session" on the row instead of reaching for a context that cannot open it. The fixture fires that callback too, so its proxy assertion — `usage service must be injected` — holds by construction, and the rows keep reading through the same namespace and the same call the pill uses.
595
+
596
+ Final-shape validation: `npm run test:ci` passes **455 tests in 30 files** with a byte-identical `check:dist`, and `DSH_COMPAT_CONCURRENCY=4 npm run test:compat` reports `PASS: installed-package compatibility across 9 host(s)` in 292.5s — the same command failed all nine hosts on the double-mounting revision and all nine again on the mount-waiting one.
597
+
598
+ ## Usage pill panel: model-selector card, explicit account choice (2026-10-05)
599
+
600
+ The panel behind the composer's usage pill was rebuilt in the shape of the host's own model selector — a 320px card of 42px rows on a raised tile — after a rendered four-way comparison (`mockups/usage-pill/option-a…d.png`) picked the drill-down. The account now sits on a card row (state dot, name, `Preferred`, `Switch ›`), and `Switch ›` swaps the card's body for the account list, whose rows tick the current account and answer ↑ / ↓ or a click; Escape steps back to the reading instead of closing the panel, and a refused switch keeps the list open with the failure notice.
601
+
602
+ The switch view says what it does: "Later requests in this profile use this account. The consumption order stays on the Settings page." This list is the user's own choice of which account spends now; the ordered fallback belongs to the Settings card, which is why the panel offers no order affordance.
603
+
604
+ The modal hint line ("Account usage · used percentage · refreshes every minute") and its two copy keys are gone, and the native `<select>` went with them: an OS-rendered option list cannot be styled to match the host menu, which is what made the old picker look foreign. The three windows became one row each — label, 10px bar, reading — with the reset stamp under it and the refresh stamp in the card's foot. The bar stays the native `<progress>` element with its `high` / `limited` classes, so the colour tiers, their roles, and every existing bar assertion survive the redesign.
605
+
606
+ Validation: `npm run test:ci` passes **459 tests in 30 files** — four new cases cover the card row and the departed picker, a successful drill-down switch, a refused one, and the arrow / Escape keys — with a byte-identical `check:dist` and both TypeScript projects clean. The installed-host fixture was A/B'd against the pre-change bundle in the same warm consumer: 1.47s before, 1.52s after. Local `test:compat` runs before that measurement tripped the script's 30s per-fixture ceiling on cold Windows file scans (the same fixture runs in 1.5s warm), so the ubuntu CI job is the gate that settles that matrix.