# GoodBuddy 0.12.3 发布前审查 ## 状态 发布候选验证完成。代码问题和发布前 UI 逻辑问题已完成主线程复核与聚焦 回归;真实模型、当前源码 Agent 的共享 Linux x64 Host E2E、全量测试、 类型检查、Lint 和获批双语发布说明校验均已通过。Desktop/Agent 已分别升至 `0.12.3`/`0.11.17`。本次按用户选择不构建 LoongArch 实验预览;剩余操作 仅为审查暂存内容并提交,未经后续明确授权不 push、tag 或发布。 ## 审查范围 - 原始审查范围:`f18e4abb3c0e8c9e7c5c870cb50a67e9ed8880fe` 至 `4e60a4ca616846fbfc312932456e1a1e7e0a0388`。 - 发布比较基线:Desktop `v0.12.2` (`c52732441aba599e643211b3d2175c8ff7027e26`)。 - 后续补充:Linux arm64 上本机 OpenCode Runtime 切换性能。 - 用户已有的 Desktop Pet 设计提交 `374968d50b3f1ae30165a5e12adf7f6213b6c00c` 保持不变,并纳入最终 发布范围核对。 审查分别覆盖 Desktop/UI、文档与构建、远程执行、模型 Runtime、 稳定性以及性能/KISS。Subagent 服务曾出现无 Token、Provider 暂不可用、 认证失败和本机 `EPERM`,这些基础设施失败未计为审查完成;六个方向最终 都获得了结果,所有候选又由主线程沿生产调用链复核和去重。 ## 已验证并修复的问题 ### 1. 默认直连模型遗漏连接级请求设置 `createAgentRuntime()` 的默认直连聊天路径没有把当前模型连接的 `requestHeaders`、`requestBody` 和 `maximumOutputTokens` 交给 `ModelAgentRuntime`。设置页显示已保存,但普通聊天不会采用这些值。 处理:默认直连路径与独立 profile 路径使用相同字段,并通过真实请求 mock 验证 Header、Body 和输出上限到达 Provider 请求。 ### 2. 切换 Embedding 连接会清空无关模型字段 Main 在切换当前 Embedding 连接时重建 `modelProfiles`,遗漏 `maximumOutputTokens`、自定义 Header 和自定义 Body。一次向量连接切换 会静默清除已保存的 LLM 设置。 最终处理:不再从完整设置 snapshot 重建输入。切换操作进入 `RuntimeSettingsStore` 的串行队列后,只验证并更新 `activeEmbeddingConnectionId`;失败回滚也只在当前值仍等于该次选择时恢复 这一字段,因此不会丢失模型 profile、API Key keep 语义、Runtime 选择、 环境覆盖或并发保存的其他设置。 ### 3. ACP 入站背压过早恢复 远端 ACP 队列消费一个事件后就恢复入站,即使剩余项目数或字节数仍达到 上限,积压时会反复暂停和恢复并继续放大内存压力。 处理:统一使用 `pendingUpdateLimitReached()`,只有项目数和字节数都降到 阈值以下时才恢复输入。 ### 4. Continue 截断后可能重复或乱序显示答案 Continue Host 的事件流出现 gap 后仍继续转发后续 delta,最终 fallback 又可能输出完整答案,导致已显示内容重复或乱序。 处理:一旦确认事件流溢出就停止转发后续 delta;最终文本只有在它确实以 已显示文本为前缀时才追加未显示后缀。 ### 5. 直连模型可能重放已到达 Provider 的请求 普通 `ECONNRESET`、Header/Body timeout 和 GoodBuddy 自身请求 timeout 并不能证明 POST 尚未到达 Provider。自动重试这些错误可能重复生成和计费, 且每次尝试会重新开始请求时限。 处理:自动重试仅保留明确的连接前失败,包括 DNS 临时失败、连接拒绝、 网络/主机不可达、connect timeout,以及明确发生在 TLS 建立前的 reset。 普通 reset 和请求阶段 timeout 不再自动重放。 ### 6. 远端 Ask 不能只依赖 ACP 权限回调 OpenCode 1.18.9 的默认 build Agent 最终权限包含通配 `allow`。若 Runtime 未产生 `requestPermission`,仅在 ACP 回调中拒绝写工具不能形成完整的 Ask 只读边界。 处理:Ask 启动同时注入顶层和 build Agent 的 `permission: "ask"`; Execute 不注入该覆盖。受管模型桥 helper 增加固定 `--work-mode` 参数, 让直接 Runtime 和模型桥 Runtime 使用同一规则。构建、双语功能清单、 远程 Host 技术设计及源码注释同步改为这条双重边界,不再描述已撤回的 read-only sandbox。 实际使用隔离配置运行 `opencode debug config` 和 `opencode debug agent build --pure` 验证过 OpenCode 1.18.9 的配置优先级。 曾评估独立 HOME/XDG 与 `--pure`,但它们会隐藏用户正常环境或插件,已因 可能影响功能而撤回。 ### 7. Main 中保留了一套没有生产调用者的旧模型网关 `RemoteModelGateway`、`ModelCallOperationStore` 及其共享 operation contracts 只被自身测试和旧 Main 网关引用。真实远端调度由 Agent 侧 `AgentModelGateway` 承担,保留两套实现会让修复落到错误路径。 处理:删除无生产构造者的 Main 旧实现及孤立测试;把仍在使用的 resolved profile digest 移入 `managed-model-bridge.ts`。摘要继续绑定请求定制, 但明确排除 API Key。 ### 8. Linux arm64 切换 OpenCode 会阻塞在用后即毁的冷启动 Composer 切换先同步等待一次 `agent.getStatus()`。旧路径会读取已分配 Skills/MCP、创建临时配置和全新 XDG 目录、启动 `opencode serve`、等待 health、调用 `session.list`,随后立即杀死进程并递归清理。切换成功后 原生能力清单还会执行另一次独立冷启动,首次真实请求再启动可复用实例。 此前提高 30 秒上限、改用 health 和跳过 `--version` 都没有消除这次 阻塞冷启动。 处理:本机受管 OpenCode 的被动状态检查只解析设置、模型凭据和已选择 路径,不读取 Skills/MCP,也不启动 Server。设置中的显式连接测试、原生 能力清单和首次真实请求仍执行完整启动、health 与连接检查。状态探针仍 为一次性对象,不把空闲 OpenCode Server 留在执行缓存中。 Windows x64 上仓库现有真实 bundled OpenCode 回环探针显示每次冷启动约 1.8 秒;历史 Linux ARM 回归已证明同一阶段可能超过 10 秒。修复后的被动 状态单测为毫秒级且断言没有 spawn、client 或 health 调用。 ### 9. OpenCode 被动状态可能验证错误的 fallback 路径 被动状态初版先让 validator 搜索可用二进制。若用户明确配置的路径已被 删除,validator 可能改为找到 bundled 或 PATH 中的另一个候选并报告就绪, 但真实启动仍会选择那条失效的配置路径。 处理:先按真实 launch 的优先级只选择目标路径,再对这个精确路径执行 不启动进程的 canonical-file 校验;校验阶段禁止 fallback 到其他候选。 ### 10. 自定义 Header 可能保存 Fetch 无法编码的值 原 schema 会接受中文等 UTF-16 code unit 大于 255 的 Header 值,但 Runtime 构造 Fetch `Headers` 时会因不符合 ByteString 要求抛错。用户会 看到设置保存成功,实际请求却在发送前失败。 处理:保存和 Runtime 共用的 schema 现在同时拒绝控制字符及 code unit 大于 255 的值,并增加中文 Header 值回归用例。 ### 11. 设置保存进行中仍可编辑并丢失新输入 保存按钮会进入 pending 状态,但 textarea 等设置控件仍可修改。旧保存 响应返回后会用已保存 snapshot 覆盖这段新输入并清除 dirty 状态。 处理:设置主体在保存或连接测试期间统一设为 `inert` 和 `aria-busy`,避免产生无法归属到当前请求的新草稿,同时保持现有异步反馈。 ### 12. Embedding-only 更新可能写回环境值或覆盖并发保存 Embedding-only helper 曾从应用环境变量覆盖后的 effective profile 生成 写回输入。在环境临时指定 Anthropic/API Key 时,切换 Embedding 连接可能 把这些有效值持久化进原本的 OpenAI/no-auth 配置。该 helper 还会在设置 串行队列外读取完整 snapshot;若另一项设置保存先入队,旧 snapshot 随后 可能把新保存内容整体覆盖,失败回滚也有同样风险。 处理:删除整份设置重建 helper,改为在 `RuntimeSettingsStore` 队列内只 验证并更新 `activeEmbeddingConnectionId`。失败回滚也只恢复这一字段; 若期间用户已选择其他连接则不覆盖。测试覆盖环境覆盖移除、并发完整保存 和窄回滚后其他设置仍保留。 ### 13. 内置浏览器卡片没有说明仅 Execute 可用 “能力与工具”中的浏览器卡片只显示通用的“按模式读写”,但后端只在 Execute 模式提供 browser tools,容易让 Ask 用户误以为当前模式可用。 处理:浏览器卡片增加双语“仅 Execute / Execute only”摘要,其他 MCP 卡片和后端权限逻辑不变。 ### 14. 长任务活动展示没有实际容量边界 候选已取消“超过 100 个工具便终止 Runtime”的功能限制,但同时移除了 消息中工具、子 Agent 和交错 block 数组的所有保留上限。长任务会让 Renderer 每次事件反复复制并线性搜索不断增长的数组,恢复与 SQLite metadata 也会持续变大,和文档所述“只截断本地展示副本”不一致。 处理:Runtime 继续不受工具调用数限制;每条回复的本地副本最多保留 500 条工具/子 Agent 活动和 2,000 个交错展示块。超出后显示持久的双语 提示,已保留活动仍可更新状态,原生任务、模型轮次和工具执行均不取消。 该上限高于 `v0.12.2` 已发布的 100 条 schema 上限,不破坏已发布数据。 ### 15. 持续 journal 背压会高频轮询 SQLite 远端 Runtime 输出在 journal 暂时满时每 25 毫秒重新执行一次同步 SQLite append。Desktop 断连或消费者较慢数分钟时,每个受阻 binding 都会每秒 触发 40 次无效事务和事件循环唤醒。 处理:第一次仍立即尝试,失败后从 25 毫秒开始指数退避,最高 1 秒; 容量恢复、取消、deadline 和 Runtime 输出暂停语义不变。 ## 候选问题处置 - Desktop/UI 首轮审查未发现 P0-P3;发布前第二轮按 Runtime 切换、 模型/Embedding 设置和 MCP/工具展示三组检查,共确认并修复上述 5 个 可复现问题。 - 文档与网站校验未发现发布阻断问题。 - 不为 Ask 使用独立 HOME/XDG,也不加 `--pure`,以免禁用正常插件或用户 环境。 - 不让普通状态或原生清单探针永久进入执行缓存,避免恢复“仅查看状态便 留下多个空闲 OpenCode 服务”的已修复问题。 - 未发现 Main 旧模型网关的生产、文档或导出调用者;Agent 侧真实网关及 共享 wire contracts 保留。 - 删除 `canonicalModelRequestHeaders()`;其唯一调用结果会立即进入已有 `canonicalJson()`,后者已按稳定 code-unit 顺序递归排序对象键。直接传入 Headers 对象转换结果可保持摘要不变并移除 locale 排序重复。 - OpenCode/ACP 在单次活动 Prompt 内保留增量工具身份,以正确归并后续状态 并在终态识别未完成/失败工具;这些 map 随 Prompt 结束释放。本轮只限制 Renderer/Main 的持久展示副本,不为 Runtime 增加会中止功能的工具配额。 - 未对没有具体失败路径的全局配额、持久恢复或额外安全层建议增加框架。 ## 当前验证证据 - `npm run typecheck`:通过。 - `npm run lint`:通过。 - `npm test`:323 个测试文件通过,8 个文件按环境跳过;3,471 项通过, 55 项跳过。 - 发布修复聚焦矩阵:13 个测试文件,427 项通过,6 项按环境跳过。 - OpenCode/SelectedRuntime 聚焦矩阵:79 项通过。 - UI 逻辑矩阵:7 个测试文件,319 项通过;其中相关修复矩阵 4 个文件 224 项通过,OpenCode 单文件 62 项通过。 - 最终审查修复矩阵:8 个测试文件、389 项通过;最后一次受影响子集 4 个文件、286 项通过。受影响文件 ESLint 和 `git diff --check` 通过。 - 真实 bundled OpenCode 回环 Provider 探针:2 次本机模型路由均通过, 每次约 1.8 秒冷启动。 - 2026-09-03 共享 Linux x64 Host 使用当前源码 profile 和已登记真实 OpenCode 1.18.9 运行 `debug agent build --pure`:Ask 的最后一条 `*/*` wildcard 为 `ask`,Execute 为 `allow`;Ask 保留正常 HOME/XDG, Execute 不注入配置覆盖,直接 Runtime 与 model bridge 规则一致。共 0 次模型调用,唯一 `/tmp/goodbuddy-e2e-release-profile-*` 目录已清理, 共享安装未修改。 - 直连 Runtime 自定义 Header+Body 真实最小请求:1 次调用通过;DeepSeek Harness 自定义 Header 真实最小请求:1 次调用通过。两者均由本机 loopback 先观察最终请求再转发,未启用工具或附件,也未输出凭据。 - 使用当前源码构建的测试签名 Agent `0.11.17` Linux x64 bundle,在共享 Host 的唯一 `/tmp/goodbuddy-e2e-release-*` HOME 中完成真实 attach、 workspace validation 和 OpenCode 1.18.9 ACP Runtime 验证。两个并发 Ask 分别通过原生读取工具返回各自证据文件,Execute 通过原生 Bash 创建并读回 `execute-output.txt`;3 个 Runtime Prompt 共发生 6 次真实 Provider 请求,每次均观察到连接级自定义 Header+Body,6 个请求 Body digest 全部不同,未发生重放。 - 同一 Host E2E 扫描隔离 Agent state 的 7 个文件、170,534 字节,未发现 API Key、自定义 Header 值或自定义 Body 值。测试 Agent、Provider 代理 均已精确停止;独立清理探针确认剩余测试目录和进程均为 0,共享安装未修改。 该 bundle 只使用临时测试签名,不是 production 构建或发布产物。 - Desktop `0.12.3`、Agent `0.11.17` 的版本一致性脚本通过, `git diff --check` 通过。 - 用户已批准 `0.12.3` 精确中英文发布说明并选择不构建 LoongArch 实验预览; `npm run release:notes:verify` 通过。发布说明源码测试不再把某一历史候选 恰好超过 500 字符的内容强加给每个后续版本,而是用独立 schema 样例持续 验证长场景条目不受任意字符上限限制。 - 当前没有可用 Linux arm64 真机,因此本轮未取得 arm64 实机切换耗时; 该限制不冒充 x64 结果。修复已由不 spawn 的被动状态单测、Windows x64 回环时序和共享 Linux x64 当前源码 Runtime 路径共同覆盖。 ## 提交与发布边界 1. 提交前审查暂存内容,排除本机 `.diff*.txt`、临时 bundle/probe、凭据、 日志和其他用户私有产物。 2. 提交后报告精确 commit。只有取得用户后续明确授权,才可 fetch/push 两个 `main`、等待 main CI 并创建 `agent-v0.11.17` 或 `v0.12.3` 标签。