Skip to content

fix: 设置保存提示透出宿主原文;带路径前缀的 apiBase 不再探活误报 - #113

Closed
ExSchwi wants to merge 1 commit into
Mars-Sea:mainfrom
ExSchwi:fix/settings-diagnostics-and-probe-prefix
Closed

ExSchwi wants to merge 1 commit into
Mars-Sea:mainfrom
ExSchwi:fix/settings-diagnostics-and-probe-prefix

Conversation

@ExSchwi

@ExSchwi ExSchwi commented Oct 31, 2026

Copy link
Copy Markdown

关联 issue

改了什么

#110 保存设置失败时的提示不可诊断

原先 RemoteSettingsScope.mutate() 只把结果压成 'conflict' | 'failed',宿主返回的原文在那一行就被丢掉了,UI 于是只能显示固定的「配置提交未获确认;密钥未写入」。真实原因(例如 atomic-write: timed out waiting for the writer lock at <DSH_HOME>/profiles/web/package.json.lock)永远到不了用户眼前。

  • RemoteSettingsScope 新增 private mutationFailure 与公共 lastMutationFailure():被拒时记住 response.error?.message,下一次成功写入清空。
  • SettingsScope 接口加可选 lastMutationFailure?()(旧测试桩不受影响);createSettingsScope() 的返回字面量显式转发该方法。
  • SettingsWriteResult 新增可选 configError;commit() 改为返回 { confirmation, failure },四条失败路径(不可写 / 宿主没有原子 mutate / 待提交字段变了 / scope 报错)各自给出原因,drain() 的 catch 用 readableErrorText(error)。
  • settingsWriteNotice() 追加一条 writeConfigFailedDetail(宿主返回:{error},超过 200 字符截断);只有看起来瞬时的原因(PROFILE_BUSY、EAGAIN/EBUSY、ETIMEDOUT、writer lock、timed out 等)才补 writeConfigRetryable。
  • writeConfigUnconfirmed 去掉误导性的「the key was not written」:保存失败与密钥是否写入是两件事。

#111 带路径前缀的 apiBase 一定探活误报;断连提示不含目标 host:port

  • probeCommandCode() 原先是 new URL('/', options.apiBase),把所有路径前缀折成 host root。真实模型请求是字符串拼接 ${apiBase}${path},http://127.0.0.1:18797/bili/https://api.commandcode.ai 这类前缀反代只在 prefix 内路由,探到根路径必然 404 并被判成 http-error。现在改用新增的 probeTargetOf(apiBase):保留路径前缀、补尾斜杠,探活目标就是配置的 base 本身。
  • getUsage() 的失败串从端点 path 改成 ${base}${path},于是 detail 里带完整的 scheme://host:port 与反代前缀——端口从 18797 漂到 18798 时,提示直接给出试过的地址,而不再只是「所有请求都没有到达服务端」。
  • usageNetworkHint / errorNetworkHint 两种语言的文案改为说明每条失败都会列出它尝试的地址。

验证

  • npm run typecheck 通过。
  • npm test 全量 1148 项通过(新增 7 项:probeTargetOf 单元、path prefix、reverse-proxy prefix、getUsage 失败记录保留前缀、lastMutationFailure 保留/清空、提示带宿主原文、过长原文截断)。
  • npm run build 成功,lib/ 构建产物按仓库惯例用 git add -f 一并提交。

没有动的

  • package.json 版本号与 CHANGELOG.md 留给发版流程。
  • 设置页的其余交互与文案未改。

Mars-Sea#110 保存设置失败时的提示不可诊断

- `RemoteSettingsScope` 记住最近一次被拒的宿主原文(`lastMutationFailure()`),
  被拒的写入不再把 `error.message` 丢在 `mutate()` 里。
- `SettingsWriteResult` 新增可选 `configError`,`commit()`/`configure()`/`drain()`
  三条失败路径都把它带到 UI;`SettingsScope` 接口加可选方法,旧的测试桩不受影响。
- `settingsWriteNotice()` 追加「宿主返回:…」(原始异常超过 200 字符截断),
  只有看起来是瞬时的原因(锁超时、`PROFILE_BUSY`、`ETIMEDOUT` 等)才补一句「稍后重试通常能成功」。
- 英文 / 中文的 `writeConfigUnconfirmed` 去掉会误导的「密钥未写入」——保存失败与密钥
  是否写入是两件事,真实原因现在由新增的两条文案给出。

Mars-Sea#111 带路径前缀的 apiBase 一定探活误报,断连提示不含目标 host:port

- `probeCommandCode()` 不再 `new URL('/', apiBase)` 打回 host root,改为新增的
  `probeTargetOf(apiBase)`:保留路径前缀、补尾斜杠;真实模型请求本来就是
  `${apiBase}${path}` 字符串拼接,前缀反代只在 prefix 内路由。
- `getUsage()` 的失败记录从端点 path 改成 `${base}${path}`,`detail` 里因此带上
  完整的 `scheme://host:port` 与反代前缀,端口一变就能从提示里看出来。
- `usageNetworkHint` / `errorNetworkHint` 说明每条失败都会列出它尝试的地址。

两处行为变化都由新测试覆盖(service-probe 3 项、adapter 1 项、settings 2 项、
settings-scope 1 项),全量 `npm test` 1148 项通过。
@Mars-Sea

Copy link
Copy Markdown
Owner

谢谢这份 PR,也谢谢前面两条 issue 的复现——诊断是准的,两个问题都成立。

当前状态:这两条 issue 的修复已随 v0.16.3 发布(Release),#110、#111 也已在 issue 下回复。因此本 PR 与已发布的实现重叠,分支又与当前 main 冲突(12 个文件,基于 079964e),合并会回退已发布的行为,先关闭。不是否定这份工作,下面逐条说明差异和没有采纳的部分。

与已发布实现的两处差异

1. 探活目标:${apiBase}/alpha/whoami,而不是 base 根

你保留前缀探 base 根,能解决 bili 这类「整站映射」反代。我们改探真实端点,是因为还有两类情况探根解决不了:

  • 反代只转发 /alpha/*、根路径返回 404 —— 探根仍会误报,而 /alpha/whoami 是通的;
  • 根路径被别的服务占用并返回 200 —— 探根会假报正常。

代价是判定语义从「镜像官方状态页 monitor」变成「本机到鉴权层是否可达」,所以 401/403 也计为可达(探活刻意不带凭据,401 就是预期回答),404/429/5xx 仍判异常。这个口径写进了模块注释与《决策记录》第十一章。

2. 用量失败串:路径 + 目标 origin,另加中文原因

你拼完整 URL,信息更全;已发布版本改为 ${path}: <原文>;连接被拒绝(ECONNREFUSED);目标 http://127.0.0.1:18797 —— 目标只保留 scheme://host:port、丢弃路径与查询,避免反代前缀或可能夹带的凭据进到界面;另外补了错误码映射(ECONNREFUSED/ENOTFOUND/连接超时等),原始码留在括号里。

3. 保存失败原文:随返回值透传,而不是 scope 上的状态

你的 lastMutationFailure() 能用,但它是 scope 上的可变状态,可能被后续另一次写入的失败覆盖;已发布版本把 message 随 SettingsMutationReport 返回值一路带到提示层,没有陈旧状态。文案方面两边一致:不再把配置写入失败说成「密钥未写入」,已发布版本再进一步按「本次是否提交了密钥」分两种措辞。

你这里做得更好、但这次没进版本的两点

  • usageNetworkHint 文案:你改成「每条失败记录都会列出它尝试的地址,请核对主机与端口」。已发布版本把地址放进了详情,但顶部提示仍是旧的「请检查网络连接或 API 地址设置」,没告诉用户去哪看。这点值得跟进。
  • 可重试判定(writer lock / timed out → 「稍后重试通常能成功」):当时以「宿主只回自由文本、没有稳定错误码,按正文关键字猜会重演 400 JSON node count cannot exceed 32768 被归到通用拒绝,上游原文被丢弃 #96 那类误判」为由没做。这是保守取舍,不是判定这件事没价值。

这两点欢迎单独开 PR(基于最新 main),我们按上面的口径一起看。

我们的流程失误

这个 PR 在 2026-10-31T08:40Z 就开了,我们在 14:45Z 发布了同一批修复——中间没检查过 PR 列表,只看了 issue,造成了重复劳动。已把「接 issue 时查一次 PR、发版前再查一次,已有 PR 不得静默取代」写进仓库的 AGENTS.md,避免再发生。

再次感谢。

@Mars-Sea Mars-Sea closed this Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants