Skip to content

2.0.25-linux

Choose a tag to compare

@github-actions github-actions released this 03 Oct 08:30
· 278 commits to main since this release

OpenMinis-Linux 2.0.25-linux

  • versionCode 225
  • 数据库仍为 21(与 2.0.23 / 2.0.24 相同,无迁移)
  • 本版全部是可靠性与可用性修复,无破坏性变更;从 2.0.24 直接覆盖安装即可

1. 模型列表:一次失败不再被当成「密钥无效」

问题:添加自定义供应商后模型选择器是空的,手动刷新也没用。

四个目录接口(OpenAI / OpenRouter / Gemini / Anthropic 兼容端点)此前把第一个非 2xx 响应当作终局:遇到 401/403 就清掉本地模型缓存并返回空列表。对已有供应商,refreshModels 的 isNotEmpty() 闸门会保住已种子化的目录,所以看不出来;但对新添加的供应商,"保住已有的"等于什么都没有——密钥明明是好的,选择器却永久为空,而手动刷新走的是同一段代码,同样救不回来。

这不是假想。写入本次修复时对某个公共 new-api 中继实测:

观测 结果
连续 8 次调用 全部 HTTP 401 Invalid token (request id: …)
同一密钥约 2 分钟后 HTTP 200,返回真实 completion
真正错误的密钥 401,但正文是另一种文案

即网关把「你的凭据无效」和「我为这次请求挑的上游通道凭据无效」混为同一个 401,而后者会自愈。

修复:靠正文文案区分只会覆盖到恰好被测过的那家网关,所以改成结构性的——不再把单次采样当证据。

  • 新增 ModelListFetchRetry,按结果分级重试:
    • 401 / 403 → 重试 1 次(AUTH_ATTEMPTS = 2),间隔 700ms
    • 429 / 5xx / 传输失败 → 重试至 3 次,间隔 500ms → 1500ms(带 ±20% 抖动,避免并行刷新同步撞车)
    • 400 / 404 / 422 等确定性客户端错误 → 不重试。重发一个字节相同的请求不会改变答案,把预算花在这上面只会把一次配置笔误变成 25 秒卡死
  • 25 秒总预算:预算耗尽即返回最后一次响应(而不是抛合成超时),调用方原有的 !isSuccessful 分支、缓存失效与 PRESERVED 语义完全不变。真正无效的密钥只多花一次往返(约 0.7 秒)。
  • 只有最后一次尝试才驱动缓存失效与用户可见报错——这是原缺陷的核心。
  • Anthropic 的候选 base 循环(最多 4 个)按候选数平分预算,避免最坏 4×25 秒;一次刷新的总墙钟时间与单次接口一致。
  • SharedHttpClients.default 从裸 OkHttpClient() 改为显式超时:连接 10s / 读 20s / 整调用 25s。此前只有 OkHttp 默认的 10s 连接与读超时、且没有整调用上限,配合重试循环最坏情况无上界。

顺带修掉一处回归:xAI 动态目录测试原本断言"一次 401 即回落内置列表",那正是本次有意改掉的旧契约。已更新为"耗尽重试预算后才回落",并新增反向回归测试:一次 401 后恢复 200,必须拿到 live 目录而不是退回手写种子列表——退回种子列表就意味着 GH#265(构建之后发布的 Grok 型号永远不可见)复发。


2. 流式回复中途卡死现在有界

问题:回复停在半句、界面一直显示"正在输出",只有停止键能出来。

firstEventWatchdog 只守第一个 chunk,它的文档自己写着「一旦有内容到达(思考增量、正文、工具调用),看门狗退役,流跑到结束」。而全仓库没有任何 chunk 间空闲看门狗。于是唯一的界限是传输层读超时——OpenAIProvider 是 600 秒,Anthropic 与 Gemini 客户端是 10 分钟。中继吐出第一个 delta 之后静默,界面就会卡在"streaming"最长十分钟,而且因为从未抛出异常,既有的重试与回退机制一次都不会触发。

这与厂商无关:任何在生成中途丢弃或停住 SSE 连接的网关都会这样(本次实测到 5 路并发下 1 路 60 秒零字节超时、串行下 Recv failure: Connection reset by peer),移动网络下 TCP 存活但停止投递也是同一形态。

修复:新增两阶段算子 streamStallWatchdog。

  • 阶段 1(尚无任何 chunk):沿用既有 45s / 推理模型 90s——请求可能还在排队,模型也可能在首 token 前思考。
  • 阶段 2(流已存活):chunk 间静默超过 2× 首事件阈值(90s / 180s)即取消上游并抛 TransientError,交给既有重试链。连接已经证明过自己,此时的长时间静默意味着进度停了,而不是启动慢。
  • 阈值从既有常量派生,不引入第二个需要按厂商调的魔数。
  • 两阶段都抛 TransientError(isRetryable)。已核对 agent loop 的重试路径本来就会把本轮半成品块回滚到 turnStartBlockIndex,并保留用户已读文本、让新尝试写入全新缓冲区([T-android-fallback-text-rewind]),所以中途卡死重试不会重复内容。
  • 阶段 2 是可选的:idleTimeoutMs = 0 即关闭,firstEventWatchdog 正是这样委托的,其契约与回归测试 retires after the first chunk, a late stop is not killed 原样通过。
  • 采用轮询重算截止点(1 秒刻度、min(remaining, slice) 封顶)而非"每个 chunk 重启定时器":高吞吐流每秒可达数百 chunk,逐 chunk 取消并重启协程的代价远高于每秒一次唤醒;短超时依然精确。

3. 会话记忆面板收敛为「人格 / 规则」两行

问题:2.0.24 的"注入同源"面板把 6 个条目平铺成同级——人格、应用级 GLOBAL.md、会话级 GLOBAL.md、系统提示词快照、今天日记、昨天日记。用户读起来是"两项设置",看到的却是六行。

修复:分组,而不是删信息。

  • 主区「自动注入」只剩两行:人格、规则。
  • 规则可展开,展开后是原先那两个 GLOBAL.md 条目(应用级 · 只读 / 本会话 · 可编辑),顺序与注入拼接顺序一致,面板仍然如实反映模型看到的提示词。折叠态摘要(如「应用级 12 行 · 本会话 3 行」)由子条目派生,不可能与展开后看到的内容不一致。
  • 记忆日记(今天 / 昨天)与诊断(系统提示词组装快照)各自独立分区——它们是日志与地面真值,不是设置项。
  • 2.0.24 建立的注入同源语义完整保留:所有文件仍可达,只是从平铺改为分组。
  • 未配置的文件在折叠摘要里不显示为 0 行而是直接省略;两个都未配置时只显示一次「未设置」。("".lines().size == 1,直接数行会让一个从未动过的 GLOBAL.md 看起来像已配置。)

顺带修掉 2.0.24 引入的 i18n 回归:面板行标题此前硬编码中文,17 个非中文 locale 的用户看到的是中文。改为字符串资源(英文默认 + 中文),其余 locale 回落英文默认。


4. 备份恒定包含凭据,去掉「必须加密」前置条件

问题:备份里默认不含 API 密钥;想包含就必须先开加密。从这种备份恢复后所有供应商都连不上,表现为"数据丢失"。

而且这个限制只存在于手动备份 UI:BackupExporter.Options.includeCredentials 本身默认就是 true,所以定时备份与 agent 触发的备份一直都带凭据。互锁只让手动导出成了例外。

修复:

  • 去掉「包含凭据」独立开关。
  • 去掉与加密的双向互锁(此前关加密会强制关凭据、开凭据会强制开加密)。
  • 备份恒定包含凭据(API 密钥、OAuth 令牌、环境变量值、MCP 授权头)。
  • 加密开关回归本义:只决定包是否加密,不再决定包内某一部分是否存在。
  • 明文警告保留,且仍按同样三类触发(供应商 / 环境变量 / MCP 服务器)。未加密时明确提示:此备份包含你的 API 密钥、OAuth 令牌和环境变量的值,且未加密——任何拿到该文件的人都能读取。
  • 清理失效的偏好键与两条死字符串(其中副标题的「Requires encryption.」在去掉互锁后已是错误陈述)。

请留意这条改动的后果:导出的备份文件现在是含明文凭据的敏感文件。放到共享存储或 rclone 远端时,建议开启加密。


测试

  • 全量 JVM 单测通过(:app:testReleaseUnitTest、:core:model:test、:core:common:test)。
  • 本版新增 31 个测试:
    • ModelListFetchRetryTest(13)——重试策略矩阵 + 用 MockWebServer 端到端验证「401 后 200 恢复」「400 不浪费预算」「预算耗尽交回最后一次响应」
    • StreamStallWatchdogTest(6)——阶段 2 卡死、阶段 1 不变、短间隔不误杀、委托兼容
    • SessionMemorySheetGroupingTest(12)——分组结构与折叠摘要,纯函数、不依赖 Compose
  • 既有 FirstEventWatchdogTest(4)与 XAIDynamicCatalogTest 全部通过;xAI 那条按新契约更新并新增了反向回归。

安装与校验

  • 包名 com.openminis.linux,启动器名称 Minis Ultra,架构 arm64-v8a。
  • NDK r29 29.0.14206865;数据库版本 21(无迁移)。
  • 签名与校验说明见 docs/SIGNING.md。
  • 2.0.9 以来的完整中文更新日志见 docs/CHANGELOG-since-2.0.9.zh.md。