feat(tui): [EPIC-INT-01B] 引入 RemoteRuntimeAdapter,实现 TUI 客户端向 Gateway 平滑下沉#374
Conversation
完成 TUI 客户端向 Gateway 架构的平滑切流改造(绞杀者模式 Phase 1): 1. 通信客户端:新增 GatewayRPCClient 与 StreamClient,全面支持本地 IPC 管道、静默 Token 读取、超时与默认重试机制。 2. 远程适配器:新增 RemoteRuntimeAdapter,实现业务调用前置自动握手(authenticate -> bindStream),并安全桥接 run/compact/cancel/list 等 JSON-RPC 动作。 3. 强类型事件流:攻克网关 JSON Payload 的类型擦除痛点,实现 gateway.event 到 TUI 强类型事件(Message/ToolResult)的安全反射与还原。 4. 架构防线:坚守 3 大核心策略。显式拒绝工具执行回调(防后门),采用最小可用会话映射,并实现网关失联时的 Fail-Fast(拒绝自动静默回退)。 5. 平滑灰度:在 CLI Root 引入 `--runtime-mode=local/gateway` 开关,默认保持 local 物理直连,确保老用户无感,为最终解耦提供对比对照组。
修复在 gateway 模式下发起全新 TUI 会话时,因本地文件及目录缺失导致的 `file does not exist` 级联崩溃问题,打通网关接入的最后一公里。 核心改动: 1. 强化错误拦截:在网关桥接层使用更严谨的错误断言 (`errors.Is` 配合字符串安全匹配),精准拦截底层文件缺失错误,坚决防止异常向客户端冒泡。 2. 彻底落地 Upsert:重构 Runtime 层 `CreateSession` 流程,当桥接层 `LoadSession` 未命中时,复用底层正规建档能力,静默完成初始化并返回合法空会话,消除 JSON-RPC 协议缺口。 3. 存储目录前置保障:在 SQLite Store 存储层写入前,强制调用 `os.MkdirAll` 确保底层目录树(sessions/assets)绝对存在,根除因父目录缺失导致的底层写入失败。 4. 并发安全性:在 CreateSession 中增加并发创建导致的唯一约束冲突兜底,触发冲突时自动回读。
| select { | ||
| case <-c.closed: | ||
| return | ||
| case c.notifications <- notification: |
There was a problem hiding this comment.
High · Reliability/Performance
这里向 c.notifications 的发送是阻塞的。readLoop 同时负责读取同一条 JSON-RPC 连接上的通知和响应;一旦通知通道被写满(例如 UI 短暂不消费事件),readLoop 会卡住,后续响应无法被分发到 pending,在途 RPC 会整体超时/假死。
建议把通知分发改成不会阻塞 readLoop 的策略(例如独立转发协程 + 有界队列/丢弃策略),至少保证响应分发不被通知背压拖住。
| return true | ||
| } | ||
| normalized := strings.ToLower(strings.TrimSpace(err.Error())) | ||
| return strings.Contains(normalized, "not found") || |
There was a problem hiding this comment.
Medium · Error handling correctness
isRuntimeSessionNotFoundError 直接按字符串匹配 "not found" 过于宽泛,可能把非“会话不存在”的错误(例如 schema/依赖缺失类错误)误判为 not-found,然后进入 CreateSession 分支,掩盖真实故障。
建议优先使用明确的哨兵错误或 errors.Is(例如存储层定义的 not-found 错误、os.ErrNotExist、sql.ErrNoRows 等)来做精确分类,避免误触发 upsert。
|
@xgopilot 请你帮我解决CI错误问题,并根据Review 指出了当前实现中的两个重要隐患,一个是并发阻塞问题,一个是错误处理不够严谨(上轮我们为了快速调通使用了宽泛的字符串匹配)。 请立即进行以下重构以满足生产级可靠性:
请在 internal/session (存储层) 中统一定义一个哨兵错误,例如 ErrSessionNotFound = errors.New("session not found")。 让 SQLite Store 在查不到数据时严格返回这个特定的错误,或者使用 sql.ErrNoRows / os.ErrNotExist 并包装它。 在 Runtime 层的 Upsert 判断中,只使用 errors.Is(err, store.ErrSessionNotFound) 或极其精确的底层错误断言,防止其他模块的缺失错误(如依赖找不到)误触发重建流程。 请完成这两项硬核改造,并确保所有的单元测试(尤其是并发测试和边界错误测试)全部通过! |
|
已完成你要求的两项重构并推送到当前 PR 分支( 变更摘要
测试更新
验证结果
|
…ling - make gateway notification delivery non-blocking for readLoop via dispatcher + bounded queue - drop overflow notifications with warning logs to avoid RPC response starvation - introduce session.ErrSessionNotFound sentinel and wrap sqlite missing-session paths - tighten runtime create-session upsert check to errors.Is sentinel only - add regression tests for backpressure and non-sentinel error handling Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
|
@xgopilot 请你帮我解决CI/build-test失败问题! |
处理结果已定位并修复本次 根因失败发生在 修复文件:
本地验证
未执行项
|
…tion Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
@xgopilot 请你帮我解决codecov/patch的覆盖率低的问题,要将这覆盖率提升至百分之九十五! |
完成情况已完成针对 已执行
推送信息
说明
|
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
test: raise patch coverage for gateway runtime migration
|
@xgopilot 请你解决CI错误问题:undefined: newRemoteRuntimeAdapter |
处理结果已修复 CI 报错
未做事项:未引入额外重构或行为变更,仅做最小修复以恢复 CI。 |
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
|
@xgopilot 给我一个能给AI用来review的提示词,先确认当前总体方案设计,再确认有没有存在风险点(诸如方案设计、模块边界等等) |
可直接使用的 Review 提示词你现在是该仓库的资深架构审查员,请审查 PR #374( 请严格按以下顺序输出:
使用建议如果你希望它更“严格”,可在开头再加一句: |
|
@xgopilot 你现在是该仓库的资深架构审查员,以阻止潜在线上事故为优先目标,宁可误报轻微风险,也不要漏报边界破坏和安全后门。请审查 PR #374( 请严格按以下顺序输出:
|
总体方案结论结论:不通过。 依据(按你要求的三项核对):
问题清单标题:Gateway 通知队列满时静默丢事件,可能导致流式输出/完成事件缺失 标题:RemoteRuntimeAdapter 默认重试被意外关闭,握手抗抖动能力与设计目标不一致 标题:握手顺序与方案声明不一致,存在先期事件丢失窗口 标题:流解析错误事件会无条件清空活跃 run,导致取消与状态跟踪失真 标题: 测试盲区/观察项
已本地执行: |
|
@xgopilot 请你解决下述review的问题!
改为阻塞投递(结合 Context 超时防死锁),或者引入一个弹性的、能保证关键事件(如 chunk/done/error/tool_result)绝对必达的队列分发机制。 无论如何,绝不允许丢弃 gateway.event 导致客户端状态卡死。
必须严格修改为:authenticate -> bindStream -> loadSession -> run。 必须先建好流式接收通道(bindStream),再去请求会话(loadSession),防止后续业务产生副作用时事件丢失。并同步修正固定该顺序的断言测试。
在 clearActiveRun(runID) 中增加严格校验:仅当传入的 runID 非空,且真正匹配当前界面的活跃 run 时,才允许清理状态。 防止流解析产生的无 ID EventError 盲目触发清理,导致后续真正的 Cancel 操作失效。
必须改用标准的哨兵错误断言,例如严格的 errors.Is(err, session.ErrSessionNotFound) 或 errors.Is(err, os.ErrNotExist)。 只有在精确命中哨兵错误时,才允许走自动创建(Upsert)分支,掩盖真实底层故障是零容忍的。
“通知队列满/高并发场景下,流式关键事件保证不丢失”的契约测试。 “RemoteRuntimeAdapter 默认零值参数下,确实发生重试”的行为覆盖。 “触发一个无 ID 的流解析错误,断言活跃的 Run 状态未被意外清空”的防回归测试。 请严格按照上述要求逐一修复 |
修复结果已按要求一次性完成 5 个核心问题和 3 个测试盲区修复,并已推送到当前 PR 分支 已完成项
新增/调整回归测试
验证
|
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
|
@xgopilot 再做一轮审查性review,要求你谨慎分析 |
总体方案结论结论:有条件通过。当前实现已覆盖 问题清单标题:关闭流程存在并发 panic 风险( 标题: 标题: 额外测试盲区
已执行: |
|
@xgopilot 请你根据review的问题进行修复 |
修复完成已根据最新一轮 review 问题完成修复并推送。 变更摘要
新增/调整测试
覆盖了:
验证
提交与推送
未执行项
|
…fication issues - harden gateway rpc notification backpressure with fail-fast timeout - avoid close/readLoop channel panic by removing queue close path - replace runtime already-exists string matching with sentinel errors.Is checks - add ErrSessionAlreadyExists and map sqlite unique constraints to sentinel - add regression tests for close interleaving and backpressure fail-fast Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
fix: harden gateway runtime migration reliability
|
@xgopilot 请你再度对该PR进行审查性review |
总体方案结论结论:有条件通过。 本轮基于 问题清单未发现新的阻断性问题。 剩余测试盲区/观察项
本地核验
|
🎯 目标 (Motivation & Context)
本 PR 是 [EPIC-INT-01] 全链路贯通 的第二阶段(Client Migration - Phase 1)。 在前一阶段 [EPIC-INT-01A] 成功筑牢 Gateway 后端防线的基础上,本阶段的核心目标是在不破坏现有直连用户体验的前提下,利用“绞杀者模式”让 TUI 客户端具备通过 JSON-RPC/IPC 协议对接 Gateway 的能力。
打通此链路后,TUI 将从“与 Runtime 强耦合的单体架构”正式迈入“控制面与数据面分离的分布式架构”,为后续的跨端生态集成与多租户治理铺平道路。
Closes #361
✨ 主要变更 (Key Changes)
1. 客户端通信基建 (Networking Clients)
GatewayRPCClient:实现统一的 JSON-RPC 2.0 客户端(优先使用本地 IPC 管道通信)。支持静默 Token 自动加载、自定义超时,并加入了默认重试机制(提升 IPC 本地通信抗抖动能力)。GatewayStreamClient:负责消费gateway.event流式推送。攻克了 JSON 弱类型擦除痛点,实现了网关通用 Payload 到 TUIruntime.Event强类型结构体(如 Message/ToolResult)的安全反射与还原。2. 远程运行时适配层 (RemoteRuntimeAdapter)
RemoteRuntimeAdapter,实现并桥接了现有的runtime.Runtime接口。authenticate->bindStream->loadSession->run的网络调用序列。3. 平滑灰度与降级开关 (Graceful Rollout)
--runtime-mode标志(可选值:local/gateway)。gateway时,应用装配层(bootstrap.go)将自动注入RemoteRuntimeAdapter拦截并接管底层流量。🛡️ 架构红线与核心决策 (Strategic Decisions)
为保证系统从单机向网关演进时的纯洁性与安全性,本 PR 强行贯彻了以下三大策略:
gateway模式下若无法连接网关(管道不存在/网络异常),立即阻断并抛出清晰错误,绝不偷偷回退至 local 模式,避免流量逃逸和调试黑盒。resolvePermission审批流尚未在 TUI 侧完善,因此在gateway模式下显式阻断任何 AI 工具执行回调(抛出unsupported_action),拒绝绕过网关走本地后门,死守安全底线。loadSession采用最小可用字段映射策略,聚焦于打通打字机流式主链路,将 UI 私有状态与核心业务状态解耦。✅ 验收标准 (Acceptance Criteria)
request_id链路贯穿测试通过。--runtime-mode=gateway启动时,能够正常发起对话、接收大模型流式输出,且 metrics 指标 (gateway_requests_total) 正常增长。go test ./...包含所有适配层拦截策略的单元测试全部通过。