dsh-opencode-go 0.1.20 → 0.1.22

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.
Files changed (56) hide show
  1. package/README.en.md +133 -127
  2. package/README.md +131 -127
  3. package/THIRD_PARTY_NOTICES.md +3 -1
  4. package/docs/development.md +60 -8
  5. package/docs/fork-review-2026-09-25.md +191 -0
  6. package/docs/independent-package.md +2 -2
  7. package/docs/verification.md +241 -0
  8. package/lib/build-info.json +8 -1
  9. package/lib/client.js +1576 -234
  10. package/lib/index.js +7001 -338
  11. package/lib/types/account-manager.d.ts +23 -0
  12. package/lib/types/account-operations.d.ts +31 -0
  13. package/lib/types/account-policy.d.ts +16 -0
  14. package/lib/types/account-recovery.d.ts +2 -0
  15. package/lib/types/account-selection.d.ts +30 -0
  16. package/lib/types/account-service.d.ts +12 -0
  17. package/lib/types/accounts-contract.d.ts +38 -0
  18. package/lib/types/accounts.d.ts +31 -0
  19. package/lib/types/adapter.d.ts +43 -9
  20. package/lib/types/call-trace.d.ts +54 -0
  21. package/lib/types/catalog-manager.d.ts +20 -0
  22. package/lib/types/catalog.d.ts +6 -3
  23. package/lib/types/client/AccountsCard.d.ts +19 -0
  24. package/lib/types/client/Section.d.ts +3 -6
  25. package/lib/types/client/UsagePill.d.ts +4 -2
  26. package/lib/types/client/accounts-controller.d.ts +69 -0
  27. package/lib/types/client/host-compat.d.ts +7 -0
  28. package/lib/types/client/locales.d.ts +55 -1
  29. package/lib/types/client/section-controller.d.ts +17 -41
  30. package/lib/types/client/staged-form.d.ts +1 -3
  31. package/lib/types/client/usage.d.ts +1 -1
  32. package/lib/types/config-contract.d.ts +69 -0
  33. package/lib/types/config.d.ts +12 -47
  34. package/lib/types/conversion/context.d.ts +5 -1
  35. package/lib/types/conversion/image-pool.d.ts +24 -0
  36. package/lib/types/conversion/replay.d.ts +1 -1
  37. package/lib/types/conversion/stream.d.ts +1 -1
  38. package/lib/types/gateway-diagnostics.d.ts +22 -0
  39. package/lib/types/gateway-error.d.ts +2 -0
  40. package/lib/types/host-settings.d.ts +11 -0
  41. package/lib/types/index.d.ts +2 -1
  42. package/lib/types/json-response.d.ts +1 -1
  43. package/lib/types/model-metadata.d.ts +3 -1
  44. package/lib/types/models-contract.d.ts +7 -0
  45. package/lib/types/protocol-contract.d.ts +5 -0
  46. package/lib/types/protocol-policy.d.ts +4 -0
  47. package/lib/types/proxy-url.d.ts +2 -0
  48. package/lib/types/proxy.d.ts +11 -0
  49. package/lib/types/reasoning.d.ts +36 -6
  50. package/lib/types/request-control.d.ts +2 -0
  51. package/lib/types/sdk-types.d.ts +1568 -0
  52. package/lib/types/settings-bridge.d.ts +22 -0
  53. package/lib/types/usage-contract.d.ts +5 -0
  54. package/lib/types/usage.d.ts +14 -2
  55. package/lib/vendor-licenses.txt +103 -0
  56. package/package.json +8 -2
@@ -0,0 +1,191 @@
1
+ # 8 个 fork 的改进与吸收建议
2
+
3
+ 检查日期:2026-09-25。基线为本项目 `main` / `origin/main` 的 `8590c5752189658b60240b729666f814f12b27ff`(0.1.12);已通过 GitHub API 确认远端也是同一提交。检查范围是 GitHub 返回的 8 个公开 fork、全部 14 个公开分支,并结合 PR 状态、实际 diff 和当前实现判断。此次只增加研究记录,没有合并 fork、修改产品代码或发布包。[上游基线](https://github.com/Duskriver/dsh-opencode-go/commit/8590c5752189658b60240b729666f814f12b27ff) · [fork 列表 API](https://api.github.com/repos/Duskriver/dsh-opencode-go/forks?per_page=100)
4
+
5
+ ## 结论
6
+
7
+ 建议优先吸收两个独立小改动,另保留一个低优先级边界修复:
8
+
9
+ 1. **Guojing6:为 models.dev 设置独立下载超时。** 网关列表继续 10 秒,约 5 MB 的元数据快照允许更长下载时间。其 `7f699a4` 只改一个文件,可干净应用到当前 main;仍应补慢响应和超时回退测试,并评估 60 秒等待对模型选择器的影响。
10
+ 2. **vjcspy:PR #11 的历史输入校验。** 预先拒绝目前无法表达的 developer 消息、工具增删块和延迟加载工具;补丁小、没有依赖升级,已通过本次隔离类型检查和定向测试。没有已知线上触发来源,属于预防性加固。
11
+ 3. **x6nux:宿主降级后处理新版图片卸载标记。** 本次用合成历史在真实 0.1.5-rc.1 LLM 模块上复现当前转换函数崩溃,fork 同输入通过。适合只移植能力判断,并补回归测试;没有验证真实用户会话从新版降级的完整迁移流程。
12
+
13
+ 不建议整体合入 Guojing6 的兼容分支或 vjcspy 的 develop。前者将大量核心能力改为宽泛回退,后者包含发布安装脚本问题;两者的依赖和历史表示改造远大于目前已经支持 rc.1/rc.2 所需的改动。
14
+
15
+ ## 每个 fork 做了什么
16
+
17
+ 下表的“独有提交”按 `main..fork` 计算。它不等于“尚缺功能”:nam278 的功能通过整合提交进入主线,因此原始提交仍显示独有。
18
+
19
+ | Fork | 分支和独有提交 | 主要改进 | 当前判断 |
20
+ | --- | --- | --- | --- |
21
+ | [liujwcn](https://github.com/liujwcn/dsh-opencode-go) | `main`:1 | 声明 DSH 0.1.7-rc.1 支持,增加独立宿主 fixture 与兼容测试 | 当前 0.1.12 已覆盖,并进一步支持 rc.2,不必再合 |
22
+ | [Guojing6](https://github.com/Guojing6/dsh-opencode-go) | `main`:0;`feat/dsh-0.1.7-rc1-host-drift`:3 | rc.1 开发依赖、宿主 API 回退、旧工具结果转换、Windows 沙箱构建/测试调整、自包含 headless smoke、元数据 60 秒超时 | 建议单独吸收超时改动;其余按具体需求拆分 |
23
+ | [RaulLazaro](https://github.com/RaulLazaro/dsh-opencode-go) | `main` / `fix/default-reasoning-effort`:均 0 | 对不传档位就关闭思考的协议设置默认推理档位 | PR #6 已合,并修正最高档位选择,不必再合 |
24
+ | [xwxYa](https://github.com/xwxYa/dsh-opencode-go) | `main` / `fix/handle-undecoded-compressed-json`:均 0 | 处理缺失编码响应头的压缩 JSON,并限制读取/解压大小 | PR #5 已合;后来演进为请求 `identity` 的实现,不应恢复旧解压逻辑 |
25
+ | [OHeroJ](https://github.com/OHeroJ/dsh-opencode-go) | `main`:0 | 仅保留较早上游快照 | 没有独有改进 |
26
+ | [nam278](https://github.com/nam278/dsh-opencode-go) | `main` / `feat/model-limits-ui`:同一独有提交 | 按模型覆盖上下文和输出容量,设置页搜索、重置、保存/放弃 | 已通过 `9d5cea7` 整合并修复边界问题,不必重复引入 |
27
+ | [x6nux](https://github.com/x6nux/dsh-opencode-go) | `main`:1 | 0.1.5/0.1.6 图片卸载兼容、同时传像素预算和目标尺寸、处理降级历史标记 | 前两项已有等价能力;降级标记处理仍值得单独借鉴 |
28
+ | [vjcspy](https://github.com/vjcspy/dsh-opencode-go) | `master`:0;`develop`:6;`feat/history-guards`:1 | rc.1 开发基线与测试改造、历史转换及 3 类不支持输入校验 | 建议 PR #11;不整体合 develop |
29
+
30
+ ## Guojing6:最有实际收益的是慢网络超时修复
31
+
32
+ 特性分支相对当前 main 独有 `4dec86b`、`da9f044`、`7f699a4` 三个提交,共改 30 个文件,1183 行新增、520 行删除;默认 `main` 没有独有提交。这个分支以 `f65a04b` 为基线,尚未包含本项目最后的 rc.2 支持和用量进度条修复。[完整分支比较](https://github.com/Duskriver/dsh-opencode-go/compare/8590c5752189658b60240b729666f814f12b27ff...Guojing6:7f699a4e5b91792fe5e72951a1229639a775bf2f)
33
+
34
+ ### 建议单独吸收:元数据下载超时
35
+
36
+ 当前 `src/catalog.ts` 的 `/models` 和 `models.dev/api.json` 都使用 10 秒完整请求期限。当前 JSON 请求还明确使用 `accept-encoding: identity`,元数据体积明显大于网关 ID 列表。fork 新增 `MODEL_METADATA_FETCH_TIMEOUT_MS = 60_000`,只用于元数据请求,保留网关 10 秒期限。作者报告代理冷连接下载用时 13.7 秒;这是作者测量,本次未在其网络环境复测。[独立提交](https://github.com/Guojing6/dsh-opencode-go/commit/7f699a4e5b91792fe5e72951a1229639a775bf2f) · [当前目录实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/catalog.ts) · [当前 identity 请求实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/json-response.ts)
37
+
38
+ 本次 `git apply --check` 验证该提交补丁可直接应用到当前 main,无需引入 `host.ts` 或升级依赖。移植时需要覆盖:元数据慢于 10 秒但快于新期限、超时复用已缓存元数据、首次失败仅用已有备用配置、网关仍按 10 秒失败。代码用 `Promise.allSettled` 等待两个请求,目录刷新和需要新快照的请求可能因此最多等待约 60 秒;不能只考虑下载成功率。[当前 snapshot/build 流程](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/catalog.ts#L115)
39
+
40
+ ### 可以借鉴,但不整体搬入
41
+
42
+ - **Headless smoke 自动安装 tarball、通过 Node 启动 CLI**:在新建 `DSH_HOME` 中先安装插件,使验证脚本的前置条件更明确,也有助于避开 Windows `.bin` 启动差异。移植时保留本项目对 `standalone-ok` 输出的断言;fork 切换继承 stdio 后删除了该断言。[提交 da9f044](https://github.com/Guojing6/dsh-opencode-go/commit/da9f044) · [fork 脚本](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/scripts/headless-smoke.mjs#L34)
43
+ - **Windows 沙箱构建/测试适配**:改用 esbuild CLI、复制 shadow 源码树并重写 CSS import、Vitest threads、继承子进程 stdio。它解决的是作者环境下的 spawn EPERM;本次没有 Windows 环境验证。增加的构建维护成本和移除输出断言的代价,暂不值得随功能补丁一并引入。[构建脚本](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/scripts/build.mjs) · [宿主测试](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/tests/host-compatibility.spec.ts)
44
+ - **统一 `host.ts` 回退**:namespace 读取适用于已知跨版本导出差异,但这里扩展到了错误类、凭据、图片、远程服务及超时机制。例如缺少 `idleWatchdog` 时直接透传 iterator,流式空闲期限失效;缺少 LlmAdapter 时 apply 记录日志后直接返回。当前已验证的 8 代宿主不需要这些假设性降级,不建议整体采用。[host.ts](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/host.ts#L246) · [apply](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/index.ts)
45
+ - **旧 nested tool-result 图片兼容**:新增递归图片检查和已卸载图片投影,但 adapter 的图片入口仍调用宿主的 `contentHasImage`,现代宿主仅扫描顶层;仅修改 context 无法使整条请求链路完整支持旧形状。应先确认宿主会否传入这种混合历史,再统一能力检查、预算计算及转换测试。[fork context](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/conversion/context.ts) · [fork adapter](https://github.com/Guojing6/dsh-opencode-go/blob/7f699a4e5b91792fe5e72951a1229639a775bf2f/src/adapter.ts)
46
+
47
+ ## x6nux:大部分已覆盖,但降级历史边界仍有价值
48
+
49
+ `0d6e9af` 的主要改动是通过宿主能力选择图片卸载协议,并同时给附件服务传递 `maxPixels/maxBytes` 和 `width/height`。本项目已经通过 `bd3ba1f` 与 `1785150` 实现相应兼容,而且现实现保留了图片准备前的估算投影与准备后的精确投影,不宜换回整个旧模块。[fork 提交](https://github.com/x6nux/dsh-opencode-go/commit/0d6e9af74bc654358197f5f5dd323a2e8c7b39ed) · [主线图片兼容提交](https://github.com/Duskriver/dsh-opencode-go/commit/178515041e6ade11cf8e292f6880171c2d46adfb) · [当前投影模块](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/conversion/image-offload.ts)
50
+
51
+ 尚有差异的是 `isOffloaded()`:fork 只有在宿主具备新版 surface offload API 时才把 `offloaded: true` 当作已卸载;0.1.5 不理解这个标记,仍需按自己的请求预算准备图片。当前 `collectImageRefs` 不分宿主跳过所有带标记图片,旧宿主后续访问 requestImages 中缺失的图片版本时会出错。[fork 能力判断](https://github.com/x6nux/dsh-opencode-go/blob/0d6e9af74bc654358197f5f5dd323a2e8c7b39ed/src/conversion/host-image-offload.ts#L72) · [当前 collectImageRefs](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/conversion/context.ts#L106)
52
+
53
+ 本次把两份 context 分别编译到临时目录,使用实际 `dsh-llm-v015-rc1` / `dsh-attachment-v015-rc1` 模块、模拟附件读取服务和同一合成输入进行对照:
54
+
55
+ ```text
56
+ 输入:user.content = [{ type: 'image', attachment: 1 字节图片引用, offloaded: true }]
57
+ 图片请求预算:100 bytes
58
+ main:读取附件 0 次;TypeError: Cannot read properties of undefined (reading 'bytes')
59
+ x6nux:读取附件 1 次;转换成功,输出 1 个 image 块
60
+ ```
61
+
62
+ 这是转换层复现,没有通过真实 CLI 打开降级会话,也没有真实模型请求。因此建议低优先级移植这个能力判断,并明确降级时是按旧宿主规则重发图片还是统一转成占位文本;不要把 fork 的 mock 测试或本次合成测试等同于完整跨版本数据迁移验证。
63
+
64
+ ## vjcspy/dsh-opencode-go
65
+
66
+ 检查日期:2026-09-25。比较基线为本项目当前提交 `8590c5752189658b60240b729666f814f12b27ff`(0.1.12)。本节检查了该 fork 的全部 3 个公开分支、实际代码差异、PR #10 / #11 及官方 DSH 参考实现;没有修改本项目生产代码,没有安装或执行 `develop` 的生命周期脚本。
67
+
68
+ ### 分支范围与结论
69
+
70
+ | 分支 | 固定提交 SHA | 相对本项目基线 | 结论 |
71
+ | --- | --- | --- | --- |
72
+ | `master` | `8590c5752189658b60240b729666f814f12b27ff` | 完全相同 | 无新增内容 |
73
+ | `feat/history-guards` | `57646f55c57b7291b783ccf80f47acf6bc64e39b` | 领先 1、落后 0;2 文件,+71 / -6 | **建议吸收**;对应开放的 PR #11,是独立、低风险的输入校验补丁 |
74
+ | `develop` | `3f5e0550a1c5cdfedcac88445a20a4d5de9cc373` | 领先 6、落后 0;23 文件,+930 / -1224 | **不建议整分支合入**;主要是开发依赖升级适配,混入安装回归与未完成的旧图片历史兼容 |
75
+
76
+ 来源:[公开分支](https://github.com/vjcspy/dsh-opencode-go/branches)、[guards 固定比较](https://github.com/vjcspy/dsh-opencode-go/compare/8590c5752189658b60240b729666f814f12b27ff...57646f55c57b7291b783ccf80f47acf6bc64e39b)、[develop 固定比较](https://github.com/vjcspy/dsh-opencode-go/compare/8590c5752189658b60240b729666f814f12b27ff...3f5e0550a1c5cdfedcac88445a20a4d5de9cc373)。以上数量由 GitHub Compare API 与本地 Git 交叉核对。
77
+
78
+ ### 建议吸收:历史与工具声明的三项保护
79
+
80
+ PR #11 的提交直接以本项目当前 HEAD 为父提交,不需要先迁移 `develop`,也不更改依赖、锁文件、已支持宿主范围或持久化格式。可以直接 cherry-pick `57646f55c57b7291b783ccf80f47acf6bc64e39b`,再按本项目流程验证合入。检查时 PR 为 open,GitHub 报告 `mergeable: true`、`mergeable_state: clean`。本次仅评估,没有合并、关闭或评论该 PR。[PR #11](https://github.com/Duskriver/dsh-opencode-go/pull/11)
81
+
82
+ 实际改变如下:
83
+
84
+ | 输入 | 现有代码行为 | 补丁行为及收益 |
85
+ | --- | --- | --- |
86
+ | 历史中出现 `developer` 角色 | 未识别的角色会落入普通用户消息分支,可能丢失角色语义 | 转换时抛出 `LlmError`,错误码 `UNSUPPORTED_CONTENT`,避免把不支持的高优先级历史按普通用户文本发送 |
87
+ | 顶层内容块为 `tool-addition` / `tool-removal` | 不属于已支持的内容词汇,可能被转换忽略 | 明确拒绝,避免工具状态变更被悄悄丢掉 |
88
+ | 工具声明含 `deferLoading: true` | `toolsOf()` 仅抽取 name / description / parameters,延迟加载标志会丢失 | 明确拒绝,避免把延迟工具当作普通工具发送 |
89
+
90
+ 以上前后行为来自实际转换代码。不能笼统称作“原来一定会得到 provider 的 malformed request”:有些输入会得到可发送但语义错误或内容缺失的请求,这也是该补丁值得吸收的原因。[当前转换实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/conversion/context.ts)、[补丁实现](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/src/conversion/context.ts#L51-L66)
91
+
92
+ 两个历史 guard 放在现有图片角色校验之前,并由同步、异步转换路径共同调用。工具 guard 放在 `toolsOf()`,不依赖历史是否非空。三个错误与 DSH 官方 `dsh-v0.1.7-rc.2` 的参考 pi-ai adapter 一致;该标签固定到 `477b4f420553e8a52c2fbccc464d7561b239c443`,已读取实际源码核实。因为本项目开发类型仍是 `dsh-llm@0.1.6-alpha.1`,使用结构类型收窄是保留旧版本编译支持的适配手段。[fork 工具 guard](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/src/conversion/context.ts#L143-L155)、[官方历史 guard](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm-pi-ai/src/context.ts#L49-L66)、[官方工具 guard](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm-pi-ai/src/context.ts#L129-L143)
93
+
94
+ 安全与兼容性判断:补丁只读取输入、抛出错误,不写历史、附件、设置或凭据,也没有新增网络端点。正常支持的消息仍使用原有转换路径;传入这三类原本不支持的输入会由“尝试转换”变成“明确失败”,属于预期的收紧行为。PR 作者明确表示目前没有已知生产来源,这一点应按作者声明理解,本次没有独立证明所有外部插件均不可能生成它们。因此优先级是预防性加固,不应宣传为已复现的线上事故修复。[PR 的范围说明](https://github.com/Duskriver/dsh-opencode-go/pull/11)
95
+
96
+ 一个非阻塞细节:异步路径的 `deferLoading` 校验仍在最后构造 envelope 时执行,可能先读取图片附件;它保证生成请求前失败,但不是所有图片工作前都失败。此顺序来自共享的 `piContext()` / `toolsOf()` 调用,且与官方实现一致。若将来要求“无效工具声明绝不读取附件”,应单独提前做 options 校验。[异步路径](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/src/conversion/context.ts#L325-L405)
97
+
98
+ ### 已执行的 PR #11 验证
99
+
100
+ 在独立 `/tmp` Git worktree 中使用当前项目已经安装的依赖,显式运行可信的 Vitest 和 TypeScript CLI;没有运行 fork 的 `npm install`、`postinstall` 或 `pretest`。环境:macOS arm64、Node `v24.14.1`、Vitest `4.1.11`。
101
+
102
+ | 检查 | 本次实际结果 |
103
+ | --- | --- |
104
+ | 基线 `8590c575…` 的 `tests/conversion-context.spec.ts` | 22 / 22 通过 |
105
+ | guards 提交 `57646f55…` 的同一测试文件 | 25 / 25 通过 |
106
+ | guards 提交的 Host `tsc -p tsconfig.host.json --noEmit` | 通过 |
107
+ | guards 提交的 Client `tsc -p tsconfig.client.json --noEmit` | 通过 |
108
+
109
+ 新增 3 个测试分别覆盖 developer、tool-addition、deferLoading,并各检查同步与异步两条入口;同步侧还断言 `LlmError` 类型,两个入口均断言错误码与完整信息。尚未单独覆盖 `tool-removal`、空 history 配合 deferred tools、`deferLoading: false` 的正常路径,可以在吸收时补上边界用例;它们不是当前发现的实现错误。[新增测试](https://github.com/vjcspy/dsh-opencode-go/blob/57646f55c57b7291b783ccf80f47acf6bc64e39b/tests/conversion-context.spec.ts#L571-L625)
110
+
111
+ “304 tests / build 通过”是 PR 作者提供的验证声明,本子任务没有将其冒充为本次完整验证;本次直接执行的范围见上表。主项目整体检查结果应在总报告中另列。
112
+
113
+ ### develop 的 6 个提交究竟增加了什么
114
+
115
+ | 固定提交 | 改动与价值 | 吸收建议 |
116
+ | --- | --- | --- |
117
+ | [`bbef5f458b5785166ddd81aff39ce1ad5100d337`](https://github.com/vjcspy/dsh-opencode-go/commit/bbef5f458b5785166ddd81aff39ce1ad5100d337) | 开发依赖迁移到 DSH rc.1;运行时 `dsh-brand` 升到 rc.1、Schemastery 升到 3.18.4;新增 `.npmrc` 与 fixture symlink `postinstall` | 暂不迁移;并非仅 devDependency 变化,且引入下述发布包安装问题 |
118
+ | [`50c788830508f9552ab853496ba6745eb1f33ffe`](https://github.com/vjcspy/dsh-opencode-go/commit/50c788830508f9552ab853496ba6745eb1f33ffe) | 适配 rc.1 类型:`RequestMessage`、settings / client 的结构访问、图标 Medium 兜底;中间版本同时删除旧 settings 和 nested tool-result 路径 | 不能单独挑这个提交;后续两次 fix 正是在恢复这次删除的向后兼容 |
119
+ | [`455de34af4c9448f2b823db889e5e42ed5a7a321`](https://github.com/vjcspy/dsh-opencode-go/commit/455de34af4c9448f2b823db889e5e42ed5a7a321) | 用 `SettingsCompat` 结构类型恢复 0.1.5 / 0.1.6 `installSection` | 本项目原本就有 fallback,主要是 fork 的类型适配与回归修复 |
120
+ | [`10bf28b85972f3b8893743ad8bcfe85b1f94ab15`](https://github.com/vjcspy/dsh-opencode-go/commit/10bf28b85972f3b8893743ad8bcfe85b1f94ab15) | 结构方式恢复旧 nested tool-result;增加深层图片检测、nested offloaded image 文本替换和 3 个 guard | guard 选用 PR #11 的独立版本;不要原样搬入其余图片逻辑 |
121
+ | [`873ee67f4b4b0e8b7ac46572fd951db653b41d18`](https://github.com/vjcspy/dsh-opencode-go/commit/873ee67f4b4b0e8b7ac46572fd951db653b41d18) | 动态配置测试改走新版 entry form,增加 settings fallback 定向测试,改消息 source fixture,固定测试 `NODE_ENV` | 可提取少量独立测试;不宜把旧 settings 文件验证整体替换掉 |
122
+ | [`3f5e0550a1c5cdfedcac88445a20a4d5de9cc373`](https://github.com/vjcspy/dsh-opencode-go/commit/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373) | 私有 fork 版本名改为 `0.1.12-vjcspy.1` | 无上游产品价值,不吸收 |
123
+
124
+ 需要纠正“develop 才新增 rc.1 / rc.2 兼容”这一印象:当前基线已经支持新的独立 `tool` role、`configForms`、`configure` 优先 / `installSection` fallback 和 Regular / 14 图标兼容;peer 范围与八代宿主矩阵也已包含 rc.1 / rc.2。develop 中许多结构 cast 与 `RequestMessage` 类型替换,目的是用新开发依赖重新编译原有跨版本行为,不是当前用户缺失的新能力。关闭的 [PR #10](https://github.com/Duskriver/dsh-opencode-go/pull/10) 也不应重复当成待移植的新特性。[当前 settings 实现](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/index.ts#L206-L240)、[当前客户端入口](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/client/index.ts#L43-L84)、[当前验证记录](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/docs/verification.md)
125
+
126
+ ### develop 的两个主要问题
127
+
128
+ **1. 发布包增加了会引用缺失文件的 postinstall。**
129
+
130
+ 已验证事实:`package.json` 第 61 行增加 `postinstall: node scripts/materialize-fixture-links.mjs`,但 `files` 白名单第 32–38 行只包含 `lib`、patch、notices、docs、examples;`scripts` 与 `tests/hosts` 均不在包内。对固定 develop 提交执行 `npm pack --dry-run --json --ignore-scripts --workspaces=false`,确认打包清单没有该脚本与 tests 目录。这个 dry-run 未构建,所以也没有生成 lib;这里只用它核实受白名单控制的脚本/测试文件缺失,不把未构建的 lib 当作缺陷。[manifest](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/package.json#L32-L62)
131
+
132
+ 由此可以确定:按当前清单发布后,在执行生命周期脚本的普通 npm 安装中,该 postinstall 会引用不存在的文件,预期报 `MODULE_NOT_FOUND`。本次没有执行消费者安装,因此具体错误是基于文件清单与 Node 入口语义的推断,并非抓取到的安装日志。即使仅补发脚本仍不完整:脚本无条件 `readdirSync(tests/hosts)`,发布包没有此目录时又会失败;它本质上只服务开发 fixture,应留在显式开发准备步骤,或至少同时解决发布环境跳过逻辑。[脚本入口及目录遍历](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/scripts/materialize-fixture-links.mjs#L23-L35)
133
+
134
+ **2. 新增旧 nested 图片支持只补了 context,入口选择和总预算仍不一致。**
135
+
136
+ 已验证的代码事实:develop 在 context 内新增 `contentHasImageDeep()`,递归收集图片并处理 nested offloaded image;但 `src/adapter.ts` 没有任何变更,仍使用宿主的 `contentHasImage()` 决定图片模型校验和是否选择异步图片转换。官方 rc.2 的这个函数仅检查顶层;其 `requiredImageOffload()` / `projectOffloadedImages()` 也只遍历顶层。develop 的 `image-offload.ts` 只换了类型,仍把原始结构传给宿主 API。[deep 检测](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/context.ts#L60-L75)、[adapter 分支选择](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/adapter.ts#L269-L300)、[官方顶层检测](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm/src/content.ts#L126-L128)、[官方图片遍历与预算](https://github.com/deepseek-ai/deepseek-harness/blob/477b4f420553e8a52c2fbccc464d7561b239c443/packages/llm/llm/src/content.ts#L216-L333)
137
+
138
+ 据上述控制流推导,在 rc.1 / rc.2 宿主接受原始旧嵌套历史的前提下:
139
+
140
+ - 若所有图片都在旧 `tool-result` 块内部,adapter 判定没有图片,进入同步转换;新增 deep 检测随后报“需要附件服务”,即使实际上已配置服务也无法重放。它从静默丢图变成明确拒绝有价值,但不能称为完成了兼容。
141
+ - 若还有一张顶层图片触发异步路径,context 会读出并发送嵌套图片,但宿主的总量检查漏计嵌套图片,因此可能超过 `maxRequestImageBytes`。已有 offloaded 标记的嵌套图片可由新增分支替换为文本,但这不解决未 offload 图片的预算问题。[同步拒绝](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/context.ts#L257-L265)、[嵌套图片转换](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/context.ts#L115-L152)、[图片预算桥接](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/conversion/image-offload.ts#L25-L47)
142
+
143
+ 这两个情形是静态控制流推断,本次未运行完整 develop,也未证明当前生产宿主会把这种历史原样交给插件。develop 自己的提交说明亦称相关 nested 分支在已发布宿主上不可达。若以后确需迁移旧历史,应明确规范化策略,覆盖 adapter 入口、tool 配对、图片预算、offload 协议和不可变历史,而不是只移植深层 helper。新增 legacy 测试仅验证同步文本 tool-result,没有覆盖上述混合图片场景。[develop 新增测试](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/tests/conversion-context.spec.ts#L573-L618)
144
+
145
+ ### develop 中值得单独借鉴的内容
146
+
147
+ - **优先借鉴 settings fallback 的定向回归测试。** `tests/settings-compat.spec.ts` 检查旧服务注册、source / onChange 驱动路由、新服务优先 configure 三点,能防止以后升级开发类型时误删旧分支。上游无需为这些测试一起迁移依赖或改生产逻辑;迁入时补足 Context dispose,并保持现有真实宿主矩阵。[测试文件](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/tests/settings-compat.spec.ts)
148
+ - **新版 profile fixture 可以作为新增覆盖,不应替代旧文件存储测试。** develop 通过真实 Loader / Settings 加一个内存 config editor 验证 entry form 更新,思路合理;但它替换了现有旧 FileSettings 测试,且删掉“非法 baseURL 拒绝后 settings.yaml 原文仍为空”的磁盘断言。内存 fixture 无法证明持久化文件没有被改写。当前上游另有 profile compatibility fixture,新增前应先查重。[fixture](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/tests/support/profile-fixture.ts)、[动态配置测试改动](https://github.com/vjcspy/dsh-opencode-go/commit/873ee67f4b4b0e8b7ac46572fd951db653b41d18)
149
+ - **`NODE_ENV=test` 可按实际问题选择吸收。** 无条件在 Vitest 配置内设置 test 环境,是开发测试的稳定性调整,不改变发布运行时;fork 注释声称修复 production shell 影响 Vite 解析的问题,本次没有复现该场景,不能据此认定当前环境必需。[Vitest 配置](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/vitest.config.ts#L1-L8)
150
+ - **图标第三重 Medium fallback 当前收益很小。** 基线已有 Regular → 14,rc.2 包同时导出 Regular 与 Medium,所以现有目标宿主不需要走第三重兜底;develop 直接引用 `IconChevronDownOutlineMedium` 的类型也是为升级开发依赖服务,不能将这段源码机械复制回旧类型基线。[fork 图标适配](https://github.com/vjcspy/dsh-opencode-go/blob/3f5e0550a1c5cdfedcac88445a20a4d5de9cc373/src/client/Section.tsx#L22-L29)
151
+
152
+ 最终建议:先吸收 PR #11 的单提交保护;单独提取 settings fallback 回归测试作为后续小改动;保留目前经过多代宿主验证的依赖与运行时适配。develop 的整套依赖迁移、postinstall、版本名和不完整图片历史兼容均不直接合入。
153
+
154
+
155
+ ## 已经吸收或已被当前实现覆盖的工作
156
+
157
+ - **liujwcn / PR #10**:`f3acbb8` 增加 rc.1 engine/peer 范围、fixture 和测试。PR 已关闭但没有 merge commit;应按当前代码覆盖情况判断。本项目 `8590c57` 已覆盖 rc.1,还增加 rc.2 和真实 Web/Headless bundle admission 测试,因此没有缺少的 rc.1 功能。[PR #10](https://github.com/Duskriver/dsh-opencode-go/pull/10) · [当前宿主矩阵](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/tests/host-compatibility.spec.ts)
158
+ - **RaulLazaro / PR #6**:对 deepseek、zai、qwen、qwen-chat-template 设置默认 effort,优先 high,否则最高非 off 档位。提交 `f4c919f` 已在 main 历史中;主线合并时还修正原代码回退选成最低档位的问题。当前的默认与显式档位验证已经覆盖各代宿主。[PR #6](https://github.com/Duskriver/dsh-opencode-go/pull/6) · [修正提交](https://github.com/Duskriver/dsh-opencode-go/commit/c371872) · [当前 adapter](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/adapter.ts#L196)
159
+ - **xwxYa / PR #5**:针对响应头缺失而收到原始 gzip/br/deflate 字节时 JSON 解析失败,加入安全读取/解压路径。PR 已合,分支 tip `13c3436` 是 main 祖先。当前主线改为显式请求 `accept-encoding: identity`,共享有大小上限的 JSON reader,并处理瞬时连接重置;旧手动解压猜测逻辑已被移除,不应再次搬回。[PR #5](https://github.com/Duskriver/dsh-opencode-go/pull/5) · [当前 JSON reader](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/src/json-response.ts) · [主线验证记录](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/docs/verification.md)
160
+ - **nam278 / PR #3**:模型容量覆盖、搜索和重置 UI、保存/放弃流程已通过 `9d5cea7` 整合并保留贡献者署名。整合实现进一步限制显式输出预算、保留原始目录值和缓存、支持继承配置的 null 重置、避免异步混用配置;再 cherry-pick 原始版本会重复或削弱已有行为。[PR #3 关闭说明](https://github.com/Duskriver/dsh-opencode-go/pull/3#issuecomment-5774872925) · [整合提交](https://github.com/Duskriver/dsh-opencode-go/commit/9d5cea7f287e636283123b5abad1889fe6ec0797) · [容量回归测试](https://github.com/Duskriver/dsh-opencode-go/blob/8590c5752189658b60240b729666f814f12b27ff/tests/model-limits.spec.ts)
161
+ - **OHeroJ**:唯一公开分支 `main` 指向 `acefef5`,是当前 main 的祖先,独有提交 0,落后 22 个提交;没有额外实现可吸收。[固定快照](https://github.com/OHeroJ/dsh-opencode-go/tree/acefef59422bde84b1b2cedc58680d86e262613e)
162
+
163
+ ## 本次验证与范围
164
+
165
+ - GitHub API 列出 8 个 fork / 14 个分支;在隔离 Git 仓库比较固定提交的祖先关系、独有提交和 diff,未更改当前仓库 remotes 或分支。
166
+ - 当前 main 实际执行 `npm test`:Host/Client 类型检查、构建成功,**22 个测试文件、301 个测试通过**。仍有宿主 UI 包缺少 source map 的已有警告。
167
+ - PR #11 在临时副本执行定向测试:基线 **22/22**,补丁后 **25/25**;Host/Client `tsc --noEmit` 均通过。未声称此次重跑了该 PR 的完整 304 项套件。
168
+ - Guojing6 的 `7f699a4` 通过 `git apply --check`;本次没有复测作者代理网络的 13.7 秒下载,也未执行该 fork 的完整测试套件。
169
+ - x6nux 的降级图片标记差异已做转换层运行对照;没有验证真实历史文件迁移、Windows UI 或付费模型请求。
170
+
171
+ ## 分支快照清单
172
+
173
+ 所有 ahead/behind 均相对固定上游 `8590c5752189658b60240b729666f814f12b27ff`,ahead 是 fork 独有提交数。
174
+
175
+ | 仓库 | 分支 | Tip | ahead / behind |
176
+ | --- | --- | --- | --- |
177
+ | liujwcn | main | `f3acbb8f736809e1bd8e751bf03bb77972a71d4c` | 1 / 1 |
178
+ | Guojing6 | main | `f65a04bd8b8e38e4edb1ff7474b3ad946ed3b271` | 0 / 1 |
179
+ | Guojing6 | feat/dsh-0.1.7-rc1-host-drift | `7f699a4e5b91792fe5e72951a1229639a775bf2f` | 3 / 1 |
180
+ | RaulLazaro | main | `7c2c6364b175deb0a58b22bdb5cea9887545a571` | 0 / 7 |
181
+ | RaulLazaro | fix/default-reasoning-effort | `f4c919f60577213cf472e0ad556ba05d5f895863` | 0 / 6 |
182
+ | xwxYa | main | `6bbe0b3f1df95506376cdda6577db91bd7e18e80` | 0 / 12 |
183
+ | xwxYa | fix/handle-undecoded-compressed-json | `13c343641000b5e4a41e213db17fc8ce6d0585ce` | 0 / 8 |
184
+ | OHeroJ | main | `acefef59422bde84b1b2cedc58680d86e262613e` | 0 / 22 |
185
+ | nam278 | main | `293f01941c3fcde4e3cb3fced87a38f9c813e622` | 1 / 22 |
186
+ | nam278 | feat/model-limits-ui | `293f01941c3fcde4e3cb3fced87a38f9c813e622` | 1 / 22 |
187
+ | x6nux | main | `0d6e9af74bc654358197f5f5dd323a2e8c7b39ed` | 1 / 26 |
188
+ | vjcspy | master | `8590c5752189658b60240b729666f814f12b27ff` | 0 / 0 |
189
+ | vjcspy | develop | `3f5e0550a1c5cdfedcac88445a20a4d5de9cc373` | 6 / 0 |
190
+ | vjcspy | feat/history-guards | `57646f55c57b7291b783ccf80f47acf6bc64e39b` | 1 / 0 |
191
+
@@ -8,8 +8,8 @@ The package's bundle keeps the configuration entry `id: opencode-go` so existing
8
8
 
9
9
  Published `@deepseek-ai/dsh-llm-pi-ai` versions `0.1.5-rc.2` and `0.1.6-alpha.1` do not export `./conversion`. This package therefore carries the request-history, stream, and replay conversion modules under `src/conversion`, with the upstream MIT license. It does not import unpublished files or bundle the whole adapter. Protocol transport remains implemented by pi-ai. Conversion maintenance is now this project's responsibility; a future public conversion library can replace these modules once published and tested.
10
10
 
11
- The build uses esbuild and lightningcss directly. Node imports stay external; the browser factory follows DSH's `window.__ModuleLoader__.load` format, embeds CSS Modules, and allows only the declared platform module imports. A package-level builder is sufficient for this single external plugin; extracting a general DSH build-tool package would require a separate release and is not a prerequisite for installation.
11
+ The build uses esbuild and lightningcss, with Rollup to flatten the exported SDK declarations. The host bundles the required pi-ai implementation and only the OpenCode Go model catalog. DSH services, Undici, OpenAI and Anthropic SDKs stay external; DSH class identities remain shared. The browser factory follows DSH's `window.__ModuleLoader__.load` format, embeds CSS Modules, and allows only the declared platform module imports. A package-level builder is sufficient for this single external plugin; extracting a general DSH build-tool package would require a separate release and is not a prerequisite for installation.
12
12
 
13
- Multi-version host fixtures have independent manifests and lockfiles, outside the root npm dependency graph. Package installation builds only the plugin. Compatibility verification installs one generated tarball into isolated consumers and uses their ordinary module resolution; it does not substitute selected host modules into the root process. See [development and verification](development.md).
13
+ Multi-version host fixtures have independent manifests and lockfiles, outside the root npm dependency graph. Consumer installation uses prebuilt artifacts and does not run a plugin build. Compatibility verification installs one generated tarball into isolated consumers and uses their ordinary module resolution; it does not substitute selected host modules into the root process. See [development and verification](development.md).
14
14
 
15
15
  The initial source derives from the user's working copy of DeepSeek Harness on branch `oh-my-dsh`, whose HEAD was `2c8555b323f3b49eac743bafd527f9f3238bf548`, including the uncommitted OpenCode Go settings changes present when this project was created. Runtime conversion modules retain their published behavior. The original checkout is unchanged by this extraction.
@@ -1,5 +1,143 @@
1
1
  # Verification
2
2
 
3
+ ## Shared architecture and experimental Responses (2026-10-10)
4
+
5
+ Settings row selection and write acknowledgements share one host/client contract. A mount-owned catalog manager supplies discovery to both the adapter and settings. Thrown and terminal inference failures share one pure fallback policy. Account commands and durable recovery run through one serialized host service, with revision checks, cancellation, deadlines, and idempotent command IDs. Client controllers retain staged edits and delegate account mutations to that service.
6
+
7
+ The opt-in `deepseek-v4.1-flash` Responses override leaves Automatic as the default, resolves protocol compatibility on each captured request configuration, and does not mutate catalog entries or retry through another protocol. Eighteen focused regressions exercise real SDK wire payloads, tools, images, restored histories in both protocol directions, protocol errors, and captured configuration changes. Installed transcript and settings fixtures cover the override and clearing it through legacy and modern host services.
8
+
9
+ Both TypeScript projects, all 655 tests in 41 files, freshness checks for 60 shipped artifacts, all ten installed DSH generations, npm/pnpm Git installations, and pnpm tarball installation passed. The desktop-installed build also passed offline benchmark observer/statistics checks.
10
+
11
+ Twenty authorized synthetic live requests succeeded with one account, interleaved protocols, stable per-block sessions, and the same 4,096-token cap. Median first body-text latency changed from 3,340 to 3,298 ms under Default (1.3% faster) and from 3,161 to 3,085 ms under High (2.4% faster). Median total time changed from 3,920 to 3,695 ms (5.7%) and from 4,501 to 3,495 ms (22.3%). High's repeated samples instead showed 8.5% slower first text and 0.8% slower total time. Route identifiers differed between protocols; these identifiers alone do not establish the physical provider. Output lengths, reasoning tokens, caching, and small sample sizes limit conclusions: this experiment does not establish a stable latency benefit.
12
+
13
+ Three preliminary live calls were excluded from those 20 samples; the third reached its earlier 1,024-token cap without body text, prompting the shared 4,096-token cap for the formal run. Desktop settings and credential-file hashes were unchanged after the formal benchmark. No private conversation was used.
14
+
15
+ ## Token budget controls (2026-10-09)
16
+
17
+ Budget-based models now display numeric token presets. The settings model card can stage, save, discard, and remove additional budgets through `modelLimits[modelId].thinkingBudgets`. Standard presets retain the old minimal/low/medium/high selection IDs; added values use `budget:N`. Models declaring both effort and budget keep their existing efforts and gain custom numeric choices. Adaptive Haiku continues using effort and exposes no legacy budget control.
18
+
19
+ Both TypeScript projects, all 556 tests in 34 files, and freshness checks for 43 shipped artifacts passed. All ten installed DSH generations passed, including 19 reasoning streams per host with standard and custom budgets. The extended regression also verifies live settings snapshots, saved selection restoration, metadata outages, models supporting both control types, completion-format budget fields, budget bounds, and SDK reduction of a budget to preserve answer room. The first installed fixture run rejected Anthropic's query-bearing endpoint because the fixture compared the raw URL; comparing its pathname fixed the harness, and the final matrix passed.
20
+
21
+ Twelve authorized, bounded synthetic requests through the rebuilt adapter passed: Qwen 3.7 Plus Default, Off, four standard budgets, and custom 4,096/6,000 budgets; Qwen 3.8 Max Low and a custom 4,096 budget; and a Qwen 3.7 Plus tool call followed by a continuation after JSON restoration of signed assistant/tool history. Captured requests carried the selected `budget_tokens` values, Default omitted thinking controls, and the restored continuation returned `CHECK-739`. Credentials stayed in memory and the user's installed profile was unchanged.
22
+
23
+ ## Unified reasoning intent (2026-10-09)
24
+
25
+ Default now leaves thinking and effort controls to the service for all three protocols. Model information no longer declares a fallback `defaultEffort`. Switch-only models display On / Off, retain the previous `high` selection ID, and send the SDK's native switch/budget without an invented effort. Adjustable models retain their advertised levels. A shared request boundary removes automatic controls for Default while preserving other SDK output, rendering, and history configuration.
26
+
27
+ `DSH_COMPAT_CONCURRENCY=4 npm run verify` passed all 549 tests in 34 files, freshness checks for 43 shipped artifacts, all ten installed DSH generations, and npm/pnpm installation checks. Each installed host also passed 14 reasoning streams, including Default, explicit Low/Off, native On/Off, and MiMo mappings. Type checking passed for both projects. Regressions cover Haiku adaptive efforts with live metadata and an outage, all four native completion switch formats, and nonmutating payload edits.
28
+
29
+ With the user's authorized credential held in memory, 20 bounded synthetic requests through the rebuilt adapter passed:
30
+
31
+ - Haiku 5.5, DeepSeek V4 Flash, GPT-6 Luna, MiniMax M3, and MiMo V2.6 Flash: 14 Default and explicit-choice requests. Every Default wire payload omitted thinking switches and effort settings. MiniMax's picker showed On / Off, and the saved `high` ID enabled its native thinking budget without an effort field.
32
+ - Haiku, DeepSeek, and GPT: six Default requests covering tool calls and continuations after JSON restoration of assistant/tool history. All three returned the synthetic tool result `CHECK-739`.
33
+
34
+ The service decided whether to reason under Default; observed reasoning blocks differed by model. The earlier pi-ai upgrade records below describe the behavior before this change, including its previous Default-to-disabled/native-High policy.
35
+
36
+
37
+ ## pi-ai 1.1.0 and Haiku 5.5 adaptive thinking (2026-10-09)
38
+
39
+ Upgraded the private bundled pi-ai alias from 0.87.1 to 1.1.0, with its matching external transports: OpenAI 7.19.0 and Anthropic 0.129.0. The upstream OpenCode Go catalog now supplies Haiku 5.5's `forceAdaptiveThinking` compatibility flag; the existing metadata merge retains it without a model-specific production patch. Refreshed the bundled implementation, public declarations, and upstream v1.1.0 license.
40
+
41
+ The Haiku regression captures real SDK requests against a loopback gateway for low, medium, high, xhigh, max, and off, with both live metadata and metadata failure. Enabled efforts use adaptive thinking and `output_config.effort`, never `budget_tokens`; off disables thinking. Native-toggle fixtures now use DeepSeek V4 Pro because the new upstream catalog removed Qwen 3.6 Plus.
42
+
43
+ On macOS, `DSH_COMPAT_CONCURRENCY=4 npm run verify` passed 539 tests in 34 files, freshness checks for 43 shipped artifacts, all ten installed DSH generations (0.1.5-rc.1 through 0.2.1-alpha.1), and npm/pnpm installation checks. Existing three-protocol fixtures cover prompts, tools, restored replay, and continuations. Installation assertions additionally verify that a consumer keeps public pi-ai 0.85.1, OpenAI 6.40.0, and Anthropic 0.124.0 while the plugin resolves OpenAI 7.19.0 and Anthropic 0.129.0. Fresh pnpm Git and packed installs require no build approvals.
44
+
45
+ Clean `npm ci --strict-peer-deps` and `npm run test:ci` passed with Node 22.19.0 / npm 10.9.3 and Node 24.14.1 / npm 11.11.0. Regenerated the lockfile with npm 10 to retain Vite's optional esbuild peer records. The initial automated run used local fixtures; live validation is recorded below. The user's installed profile was not changed, and nothing was published.
46
+
47
+
48
+ ### Expanded live validation (2026-10-09)
49
+
50
+ With the user's authorization, used the existing default OpenCode Go credential in memory through the rebuilt plugin adapter. Requests used synthetic prompts, synthetic tool results, temporary DSH homes, no account switching, no retries, and bounded output. Credentials, headers, and private conversation data were not saved in the report.
51
+
52
+ - Haiku 5.5: default, off, low, medium, high, xhigh, and max all completed. Wire capture confirmed adaptive thinking with the requested effort for every enabled level; max produced a reasoning block. Default/off send disabled thinking. Adaptive thinking can legitimately skip a reasoning block on an easy question.
53
+ - Three real protocols: Haiku 5.5, DeepSeek V4 Flash, and GPT-6 Luna each called a synthetic `lookup_code` tool, then returned `CHECK-739` after JSON-restored assistant history and a tool-result continuation.
54
+ - Haiku max reasoning solved a probability question as 7/22. Restoring that signed reasoning history and switching to off succeeded. Cancelling after the first text delta returned ABORTED, and a subsequent request on the same adapter succeeded. A supplied temperature was omitted for Haiku as expected.
55
+ - MiniMax M3 high, Qwen 3.8 Max low, MiMo V2.6 Flash off, and DeepSeek V4 Flash low/off all completed with correct short answers. The 23 recorded live scenarios include one intentional cancellation; an additional initial tool call was repeated after fixing the test harness's attempt to mutate a frozen user message.
56
+ - An offline old/new request-body comparison covered Haiku plus six current models (DeepSeek V4 Flash, GPT-6 Luna, MiniMax M3, Qwen 3.8 Max, MiMo V2.6 Flash, Kimi K3), including system prompts, tools, effort, temperature, and output caps. Only Haiku changed: adaptive thinking and output_config replace the old budget mode. MiniMax/Qwen's budget additions to max_tokens are unchanged existing behavior.
57
+
58
+ The new upstream catalog removes Kimi K2.6, GLM 5.1, Qwen 3.7 Max, and Qwen 3.6 Plus from its built-ins even though the gateway still lists them. A Qwen 3.6 Plus Off request was rejected locally after its legacy compatibility hints disappeared. Per the user's instruction to discard obsolete models, no legacy catalog snapshots or compatibility exceptions are retained. Live metadata may still make some such models discoverable, but their old built-in fallback/controls are no longer guaranteed.
59
+
60
+ Added Haiku to the durable signed-thinking/alias/replay regression alongside MiniMax; the final `npm run test:ci` passed all 540 tests in 34 files and all 43 artifact freshness checks. No further failures were observed in the current-model scenarios; live probes do not establish exhaustive compatibility or long-running reliability.
61
+
62
+ ## Issue #40 follow-up: upstream routing evidence and misleading occupancy (2026-10-09)
63
+
64
+ The October 8 comments report the same failure on DSH's built-in provider, with other models still working in the same conversation. This weakens a plugin-only explanation but does not identify the failed request's upstream cause. OpenCode users independently report the same DeepSeek model-only HTTP 400 body: [#51990](https://github.com/anomalyco/opencode/issues/51990) and [#51201](https://github.com/anomalyco/opencode/issues/51201). The latter reports byte-identical 200/400/200 replays; [#50761](https://github.com/anomalyco/opencode/issues/50761) instead investigates route-dependent capacity limits. [#48180](https://github.com/anomalyco/opencode/issues/48180) reports a separate history/reasoning-parameter trigger. These are reporter observations, not a confirmed diagnosis of this issue, and they show why the opaque error alone cannot justify treating every such 400 as transient.
65
+
66
+ The public legacy gateway [selects a weighted provider from the session ID suffix](https://github.com/anomalyco/opencode/blob/388406238bd5ca15564a762840a2362c3a45bd9c/packages/console/app/src/routes/zen/util/handler.ts#L641-L648); its [sticky record is keyed by model and session](https://github.com/anomalyco/opencode/blob/388406238bd5ca15564a762840a2362c3a45bd9c/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts#L11). This supports investigating a session/model route that repeatedly fails. It does not prove the online failing request used this implementation: the current handler can first proxy migrated workspaces to a separate inference service. Live provider configuration and the reporter's response headers remain unknown.
67
+
68
+ A disposable consumer of the actual published `@deepseek-ai/dsh-token-meter@0.2.0-rc.2` reproduced another key distinction. The context-pressure projection went from 200,000 tokens / 20% to zero / 0% when an `assistant/attempt` carried the SDK's zero usage followed by `INVALID_REQUEST`; no compaction or surface replacement event was involved. Independently, 100 samples of 1,000,000 cached input tokens accumulated 100,000,000 tokens while latest context pressure stayed 1,000,000. The [last-sample pressure update](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/llm/token-meter/src/usage-projection.ts#L192-L196) explains why a displayed 0% does not establish empty request history, and a cumulative nine-digit counter does not establish a nine-digit prompt. These were projection probes, not a reproduction of the reporter's real UI/session.
69
+
70
+ The current [models.dev entry](https://models.dev/api.json) still advertises a 1,000,000-token context and 384,000-token output capacity, with low/high/max reasoning and `reasoning_content` interleaving. Capacity is not a measurement of this failed request. An output-cap comparison, history-shape comparison and an identical-body/session-ID comparison are useful separate experiments; changing all of them together would not isolate the cause. No real inference request or user credential was used in this investigation.
71
+
72
+ The plugin's diagnostic allowlist previously omitted Go's route/log fields. It now retains `x-opencode-log-id`, `x-opencode-endpoint-id`, `x-opencode-upstream-model-id`, `x-zen-model` and `cf-placement` when present, with the existing redaction and length bounds. The model-only JSON regression confirms that the nonempty body remains visible while 400 stays `INVALID_REQUEST`, with one attempt and the same session ID. **538 tests in 34 files**, freshness checks for **43 shipped artifacts**, and the installed **DSH 0.2.0-rc.2** fixture passed (61.3 seconds including cleanup). This improves the next capture; it does not fix or reproduce the upstream 400. The follow-up is included with the user-facing README rewrite. After integration with pi-ai 1.1.0, all 541 tests and 43 artifact freshness checks passed; the changes have not been published.
73
+
74
+ ## Issue #43: first installation without dependency script approvals (2026-10-09, unreleased)
75
+
76
+ The issue attachments report that DSH 0.2.0-rc.2 / pnpm 11.7.0 can leave the plugin dependency in `package.json` after script approval interrupts the first install. A successful retry can then miss bundle registration. Inspection of the published DSH plugin-manager source confirms that its reconciliation skips dependencies already present in the pre-install snapshot. This package change avoids the dependency-script interruption that triggers that path; it does not repair an already incomplete DSH profile or change DSH's installer.
77
+
78
+ The plugin now bundles the required pi-ai 0.87.1 code and OpenCode Go model table before distribution. The private SDK alias is a development dependency, so consumers no longer install its unrelated Google SDK and protobufjs dependencies. OpenAI 6.40.0, Anthropic 0.124.0, Undici and DSH service imports remain external. The three protocol entrypoints are imported eagerly: the initial lazy-entrypoint bundle failed the installed reasoning fixture before the first stream because a shared constant had not initialized. The final eager bundle passes that existing regression. SDK declarations are bundled too, with the original third-party licenses included.
79
+
80
+ On macOS / Node 24.14.1, the final artifacts passed **537 tests in 34 files**, both TypeScript projects, and freshness checks for **43 shipped artifacts**. All **ten installed-host generations** passed (31.7 seconds including cleanup), including DSH 0.2.0-rc.2 and 0.2.1-alpha.1; fixtures exercise model information before streaming, all three protocols, tool calls, restored replay, real images, settings, client rendering and bundle admission.
81
+
82
+ The final installation checks passed in 11.5 seconds. Fresh pnpm 11.7.0 consumers installed both Git and packed npm artifacts with explicit `strictDepBuilds=true`, separate empty stores, isolated configuration and **no build approvals or script skips**. Both installed 32 packages and passed ESM loading and public type checks; assertions confirm pi-ai, Google SDK and protobufjs are absent. An npm Git consumer independently retained another plugin's public pi-ai 0.85.1 peer resolution. Earlier tests had preconfigured Google/protobuf skips; those skips are removed. Verification used disposable consumers, not the user's installed profile. The reporter's Windows installation and real Plugin Hub UI were not reproduced here. The changes have not been published to npm.
83
+
84
+ ## Issue #40: HTTP 400 investigation and diagnostic capture (2026-10-07)
85
+
86
+ The reported intermittent rejection has not been reproduced against the real gateway. Two distinct loopback HTTP 400 responses — an empty body and `{"message":"edge rejected the request"}` — both produced the exact SDK message `400 status code (no body)` before this change. OpenAI's SDK parses the latter as JSON, extracts only its top-level `error`, and discards the other fields before pi-ai normalizes the error. Thus `(no body)` does not establish an empty wire response, a gateway routing failure, or failure to reach the model. pi-ai also initializes usage to zero and the plugin emits that usage on error; zero tokens do not establish where a request failed. The plugin itself maps 400 text to `INVALID_REQUEST`, and the real DSH 0.2.0-rc.2 provider policy excludes that code from default retries.
87
+
88
+ The adapter now captures non-success HTTP responses through a per-attempt fetch wrapper before SDK normalization. It returns the original response, retains the existing direct/proxy transport and session header, and leaves success streams alone. Error messages identify the provider/model and observed body state; when the SDK discarded a nonstandard JSON body, a bounded excerpt restores its content. Request rejection retains its prior failure classification and does not gain hidden automatic retries.
89
+
90
+ Opt-in `DSH_OPENCODE_GO_DEBUG_DIR` writes separate error records with the endpoint without authentication/query parameters, serialized request byte size, message/tool counts, final output cap and reasoning settings, a hashed session ID, HTTP status, selected response headers and the first 16 KiB of error body. Capture reads a response clone concurrently with the SDK, stops after one second or cancellation, and marks incomplete/truncated reads. API keys and configured proxy authentication, including JSON-escaped and partially truncated echoes, are redacted. Request content, cookie/authentication headers and unknown response headers are omitted. File failures add a diagnostic notice without replacing the original HTTP failure. Both READMEs describe enabling the capture and reviewing upstream error content before sharing.
91
+
92
+ A public, read-only `https://models.dev/api.json` check on 2026-10-07 advertised this model with a 1,000,000-token context and 384,000-token output limit. A mounted-plugin loopback probe using those metadata values, a two-character prompt and no explicit generation cap actually sent `max_tokens=384000`, `reasoning_effort=high` and `thinking.type=enabled`; its whole request was 205 bytes. The eventual cap and reasoning parameters deserve comparison between failing and successful requests. This is a possible parameter difference, not a demonstrated cause of the real rejection. No paid inference request was made.
93
+
94
+ Validation on macOS / Node 24.14.1: Host/Client type checks, **500 tests in 33 files**, and all **40 shipped artifacts** passed. The 13 added regressions cover empty/nonstandard/standard error bodies, successful streaming, file failures, bounded and exact-limit reads, stalled clones, concurrent session correlation, caller cancellation and secret redaction. The rebuilt package passed **all ten installed-host generations** in 38.1 seconds including cleanup. Each host exercises the nonstandard 400 through the mounted route and reads the saved request ID; 0.2.0-rc.2 and 0.2.1-alpha.1 additionally assert the actual retry policy. The earlier baseline matrix also passed before the final capture-boundary/redaction refinements. User credentials and installed DSH profiles were not changed. A real failed request's captured headers/body, plus a successful request's parameters, are still needed to determine the upstream cause.
95
+
96
+ ## Release 0.1.21: include the frosted usage panel (2026-10-07)
97
+
98
+ 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.
99
+
100
+ 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.
101
+
102
+ ## Usage panel frosted glass: the build dropped the working blur (2026-10-06)
103
+
104
+ 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.
105
+
106
+ 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.
107
+
108
+ 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.
109
+
110
+ ## Network proxy validation and DSH 0.2.1-alpha.1 (2026-10-06)
111
+
112
+ 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.
113
+
114
+ 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.
115
+
116
+ 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.
117
+
118
+ ## Account card owns credential editing (2026-10-06)
119
+
120
+ 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.
121
+
122
+ ## PR #38 account regressions (2026-10-06)
123
+
124
+ 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.
125
+
126
+ 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.
127
+
128
+ ## Usage pill collapses under a narrow composer instead of wrapping or overflowing
129
+
130
+ 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.
131
+
132
+ 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`:
133
+
134
+ * **≤ 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.
135
+ * **≤ 360px** — additionally hide the `unit` words, leaving only the bare percentages.
136
+
137
+ 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.
138
+
139
+ 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.
140
+
3
141
  ## Release 0.1.20: usage pill and settings draft fixes (2026-10-02)
4
142
 
5
143
  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 +655,106 @@ Geometry was measured in headless Microsoft Edge 154 (`--headless --remote-debug
517
655
  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
656
 
519
657
  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.
658
+
659
+ ## Multi-account hardening: add/remove transactions, fallback notice, rejected-key cache (2026-10-05)
660
+
661
+ 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.
662
+
663
+ 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.
664
+
665
+ 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.
666
+
667
+ 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.
668
+
669
+ 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.
670
+
671
+ ## Accounts card: reorder-only rows, one refresh stamp, a wide quota bar (2026-10-05)
672
+
673
+ 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.
674
+
675
+ 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.
676
+
677
+ 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.
678
+
679
+ 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.
680
+
681
+ 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.
682
+
683
+ 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.
684
+
685
+ 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.
686
+
687
+ 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.
688
+
689
+ 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.
690
+
691
+ ## Usage pill panel: model-selector card, explicit account choice (2026-10-05)
692
+
693
+ 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.
694
+
695
+ 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.
696
+
697
+ 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.
698
+
699
+ 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.
700
+
701
+ ## Accounts: the reorder gesture lands, and a fallback adopts the account that served it (2026-10-07)
702
+
703
+ Dragging an account row never reordered the list, and the card header kept naming the old account. The gesture was never the problem: in real Blink a drag starts from the handle, `dragstart` reaches the row with the handle as its target, and `dragover` accepts the drop. What went missing was the drop itself — the insertion line was a grid item of its own, so showing it pushed every row below it down by 12px and moved the pointer's own target out from under it; a release over the vacated line or the 10px gap hit-tested to elements with no handler, and the browser answers that by dispatching no `drop` at all. The mark now lives inside the row it belongs to (`.accountRow[data-drop]::after`, absolutely positioned, `pointer-events: none`), the drop handlers moved from each row to the list itself so the gaps and the mark are inside the drop surface, and the slot comes from `slotAt()` reading the rows' own midlines, which no longer move during the gesture. A drop that lands where the row already sits is a no-op instead of a refused write, and the release position — not the last hovered slot — decides where the row lands.
704
+
705
+ The automatic fallback then stopped being a per-request detour. When an account starts producing output after a fallback, the Host makes it the current account — `settings.mutate('opencode-go', [{ op: 'set', path: ['apiKeyEnv'] }], revision)` on 0.1.7 profile entries, the `llm-opencode-go` section on older Hosts — so the Settings page and the usage pill follow it, and later requests start from the account that works instead of walking into the same wall again. A manual switch outranks the adoption (the guard compares the stored value with the account the request began with, and the revision fence settles the race), a refusal — read-only Host, lost revision, no settings service, a different entry id — leaves the stored value alone and never disturbs the request, and the fallback notice survives the adoption's own settings write. The list order stays the user's fallback order; only `apiKeyEnv` moves. Copy follows the same split: 当前账号 / `Current` for the account in use, and the switch hint now reads 额度耗尽或 Key 不可用时,在尚未输出内容或工具调用前,按设置页的账号顺序尝试其他账号;一旦某个账号开始正常输出,当前账号即切换为它。
706
+
707
+ Validation: `npm run test:ci` passes **514 tests in 33 files** with a byte-identical `check:dist`, both TypeScript projects clean, and 19 new cases — 4 card-level drag cases, 5 adapter-level adoption cases, and 10 Host-wiring cases including a real `FileSettingsProvider` write and the three regression shapes an adversarial pass produced. The reorder fix was reproduced and re-verified in real headless Chromium over CDP against the bundled component: before the fix a release in the first row's top band produced `dragenter`/`dragover` and then no `drop`, with the line shifting the rows 12px; after it every scenario delivers the drop, marks sit in the gap centres, and no row moves while a mark is shown (0.00px across a three-account matrix, including the handle-column approach that had failed 5/5 before the `dragenter` acceptance was added). The adoption path was verified against the installed 0.2.x `SettingsForms` extracted from the app bundle (the volatile field accepted, `settings/document-updated` emitted, a stale revision refused, `accounts` untouched) and by a jsdom check that a published `apiKeyEnv` remounts the pill onto the new account. An adversarial pass found three counterexamples — a multi-hop no-op, legacy-row precedence, and a manual switch back inside the key-rejection window — all fixed, each guarded by a regression test that fails when its fix is reverted; the one test that did not discriminate was rewritten until it did.
708
+
709
+
710
+ ## Account selection: one decision for notices and persistence (2026-10-08)
711
+
712
+ The Host now consumes one `onAccountSettled` event for both the fallback notice and the saved account. `GoAccountSelection` owns that decision. Each request captures its sequence and account-state generation before asynchronous discovery, including prepared calls. The generation includes the settings revision, account configuration, settings-service connection and credential updates, so returning from A to C to A still invalidates an older request. The existing `onAccountSwitch` observer remains available to direct adapter consumers.
713
+
714
+ A stale outcome cannot restore a deleted account, undo a manual selection, or override a newer accepted request. Saving still uses the Host's revision-checked settings operation and never blocks streamed output. The notice is restored after a successful adoption only when the committed revision and account identity match that write; a failed adoption leaves no pending marker for a later manual edit to inherit.
715
+
716
+ Validation: `npm run compile` and `npm run test:ci` pass **537 tests in 34 files**, with **41 shipped build files** matching their source and both TypeScript projects clean. Seven additional real-`FileSettingsProvider` cases cover account removal, disabling fallback, A→C→A selection, newer-request precedence, credential replacement, delayed prepared-call dispatch, and refused-write notice cleanup. Three adapter cases cover generation capture before discovery and sequence ordering across prepared calls and model resolution. The race tests use explicit Promise gates rather than timing delays.
717
+
718
+ Installed-package compatibility also passes on **0.1.5-rc.1, 0.1.7-rc.2 and 0.2.0-rc.2**. The profile fixture now drives a real fallback through SettingsForms, verifies the saved selection without changing account order or remounting the plugin, and repeats describe/usage reads to check that the adoption notice survives.
719
+
720
+ The freshness check applies before starting a settings mutation. A mutation already accepted by the Host remains subject to its persistence and revision semantics; the plugin does not cancel or roll back an in-progress Host write.
721
+
722
+ ## Architectural hardening: failure domains, bounded preparation and atomic settings (2026-10-09)
723
+
724
+ Gateway HTTP evidence now drives failure classification before SDK message fallbacks. A 401 `ModelError` returns `UNKNOWN_MODEL` without account switching or rejected-key caching; 401 subscription limits switch with reason `quota` while retaining the key for later requests. Only explicit invalid credentials enter the rejection cache. Captured HTTP status also prevents a number such as 401 inside a 500 response message from masquerading as authentication failure.
725
+
726
+ Modern image count overflow returns the host offload request before attachment reads. Four shared permits bound native preparation across simultaneous calls, and an occurrence-weighted base64 counter checks the payload as each unique attachment completes. The normal path updates this counter in constant time; only overflow invokes the host's prefix calculation. A byte overflow aborts queued work. Cancellation keeps a native operation's permit until that operation actually settles. Legacy hosts retain their existing transient image projection semantics.
727
+
728
+ New positive integer settings `requestPreparationTimeoutMs` (60000 by default) and `requestTimeoutMs` (1800000 by default) bound discovery, credential/image preparation and whole dispatch, including account fallback. Caller cancellation ends credential/image/iterator/diagnostic waits even when their underlying service ignores its signal; SDK teardown no longer blocks cancellation. Caller aborts consistently produce an `ABORTED` finish, and deadline failures use `TIMEOUT`. Shared catalog refreshes remain independent of any single waiter's cancellation.
729
+
730
+ The advanced form commits all ordinary set/unset operations through one revision-checked `scope.mutate`. A rejected batch writes no credentials and retains drafts. Accepted settings clear separately from failed credential drafts, so retry does not repeat successful operations. New edits typed during an in-flight save remain staged. Credentials keep their original account binding when another surface switches the selected account after a failed settings batch.
731
+
732
+ Nonempty listings without valid IDs preserve the served catalog and report discovery failure; explicit empty listings still clear it. Cold listing failure reports `DISCOVERY_FAILED` rather than claiming a model is unknown. Closed SDK stream events are checked with a `never` assignment and rejected explicitly at runtime, and unknown host roles are refused. A safe `onCallTrace` summary supplies call ID, stage timing, attempt count and final outcome/code, including HTTP 200 stream truncation; observer failures do not affect requests.
733
+
734
+ Validation on macOS with Node 24.14.1:
735
+
736
+ - `npm run compile` and `npm run test:ci`: **590 tests in 37 files**, **47 shipped files** matching source, both TypeScript projects clean. The changes add 34 regression cases to the 556-test baseline.
737
+ - `DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: **all ten installed hosts pass**, including 0.1.5-rc.1/rc.2, 0.1.6-alpha.1/alpha.2, 0.1.7-alpha.1/alpha.2/rc.1/rc.2, 0.2.0-rc.2 and 0.2.1-alpha.1. The final artifact run completed in 73.4 seconds including cleanup.
738
+ - `npm run test:install`: npm Git, pnpm Git and pnpm packed npm installations, ESM loading and public consumer types all pass without source builds or build approvals; 36.1 seconds including cleanup.
739
+ - After isolating the short preparation-deadline cases from cold discovery timing, the adapter suite passes all 33 cases. `git diff --check` is clean.
740
+
741
+ The tests use loopback gateways, fake credentials and fixture attachments. Settings and credentials still lack a cross-store crash transaction or durable recovery journal. Mixed valid/invalid listing rows still use the valid IDs; public disk metadata remains insufficient to establish gateway membership after restart. Underlying native work that permanently ignores cancellation can retain its image permits until host/service recovery. These boundaries are documented in `docs/development.md`.
742
+
743
+ ## Architectural hardening, second pass: admission, attempt evidence and account recovery (2026-10-10)
744
+
745
+ Untyped quota text is now limited to HTTP 401/402/403/429. A 500 diagnostic quoting an earlier credits failure remains `SERVER` and does not switch accounts; 504 remains `TIMEOUT`. Known structured quota types retain precedence, including when an upstream uses an unexpected status. Regression cases cover the classifier and the actual account fallback path.
746
+
747
+ Image preparation now admits four active tasks and at most 32 queued tasks within the plugin module. A shared 128 MiB logical budget charges prepared raw data and occurrence-weighted base64 bytes; the adapter holds each lease through streaming and releases it on failure or consumer stop. Admission failure reports `IMAGE_RESOURCE_BUSY`. Tests exercise queue saturation, cancellation, shared reservations and a paused real adapter stream, including resource release and diagnostics. The budget excludes native decoder RSS, SDK/JSON copies and history; native work still has to cooperate with cancellation or settle before its permit becomes reusable.
748
+
749
+ Mixed model-listing rows retain valid IDs while exposing a bounded warning through the RPC codec, settings UI and Host logger. Diagnostics include counts and at most eight row positions, without copying malformed data. A clean listing clears the warning. Each dispatch now has bounded attempt details for failures and the serving account: stages, HTTP status, redacted upstream request ID, first nonempty output latency, error code and image pool occupancy/waiting. HTTP evidence files carry matching call and attempt IDs. Tests cover a quota fallback followed by success, HTTP 200 truncation, credential echoes, evidence correlation and consumer stop.
750
+
751
+ Account operations first confirm a durable, secret-free intention in their own section/profile before touching credentials. Host reconciliation completes stored-key additions and interrupted removals, preserves later account selections, and uses revision guards for metadata and intention cleanup. An addition without a stored key stays visible for replacement or removal. Recovery retries on settings/credential reconnection and updates, bounds each operation to 30 seconds, and cancels obsolete connection waits. Only exact generated ID/reference pairs authorize credential deletion; external references retain their keys. This adds recoverable eventual consistency, without claiming a cross-store transaction or isolation from arbitrary simultaneous profile edits.
752
+
753
+ Validation on macOS with Node 24.14.1:
754
+
755
+ - `npm run compile` and `npm run test:ci`: **622 tests in 39 files**, **49 shipped files** matching source, both TypeScript projects clean. This pass adds 32 cases beyond the prior 590-test suite.
756
+ - `DSH_COMPAT_CONCURRENCY=4 npm run test:compat`: **all ten installed hosts pass**, **71.4 seconds** including cleanup. The four 0.1.5/0.1.6 fixtures now recover additions and removals with real settings and managed credential files. All six 0.1.7/0.2 fixtures exercise the same operations through real profile SettingsForms.
757
+ - `npm run test:install`: npm Git, pnpm Git and packed npm installs, ESM loading and public consumer types pass without source builds or build approvals; **31.9 seconds** including cleanup.
758
+ - `git diff --check` is clean. Tests use temporary homes, fixture credentials and loopback gateways; no paid inference or user profile is changed.
759
+
760
+ The deliberate costs are bounded overload rejection, partial listings that may omit malformed models, and eventual rather than atomic account recovery. A key never committed cannot be reconstructed, old orphan keys predating the journal cannot be enumerated through the credential seam, and manually sharing generated references across profiles shares their deletion lifecycle. Cold offline membership, forced native-task termination and newer host content capabilities remain deferred; HTTP 200 in-stream errors still use the SDK/stream mapper's fallback classification.
@@ -6,5 +6,12 @@
6
6
  "@deepseek-ai/dsh-client-store",
7
7
  "@deepseek-ai/dsh-client-ui-slots",
8
8
  "@deepseek-ai/dsh-client-ui-primitives"
9
- ]
9
+ ],
10
+ "hostExternals": [
11
+ "@deepseek-ai/*",
12
+ "undici",
13
+ "openai",
14
+ "@anthropic-ai/sdk"
15
+ ],
16
+ "sdkVersion": "1.1.0"
10
17
  }