Skip to content

feat(tui): [EPIC-INT-01B] 引入 RemoteRuntimeAdapter,实现 TUI 客户端向 Gateway 平滑下沉#374

Merged
phantom5099 merged 12 commits into
1024XEngineer:mainfrom
pionxe:main
Apr 20, 2026
Merged

feat(tui): [EPIC-INT-01B] 引入 RemoteRuntimeAdapter,实现 TUI 客户端向 Gateway 平滑下沉#374
phantom5099 merged 12 commits into
1024XEngineer:mainfrom
pionxe:main

Conversation

@pionxe
Copy link
Copy Markdown
Collaborator

@pionxe pionxe commented Apr 20, 2026

🎯 目标 (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 到 TUI runtime.Event 强类型结构体(如 Message/ToolResult)的安全反射与还原。

2. 远程运行时适配层 (RemoteRuntimeAdapter)

  • 新增 RemoteRuntimeAdapter,实现并桥接了现有的 runtime.Runtime 接口。
  • 封装了标准网关握手协议:将 TUI 的业务调用透明转换为 authenticate -> bindStream -> loadSession -> run 的网络调用序列。

3. 平滑灰度与降级开关 (Graceful Rollout)

  • 在 CLI 启动入口引入 --runtime-mode 标志(可选值:local / gateway)。
  • 默认保持 local 模式,老用户无感。当指定 gateway 时,应用装配层(bootstrap.go)将自动注入 RemoteRuntimeAdapter 拦截并接管底层流量。

🛡️ 架构红线与核心决策 (Strategic Decisions)

为保证系统从单机向网关演进时的纯洁性与安全性,本 PR 强行贯彻了以下三大策略:

  1. Fail-Fast 拒绝静默降级:在 gateway 模式下若无法连接网关(管道不存在/网络异常),立即阻断并抛出清晰错误,绝不偷偷回退至 local 模式,避免流量逃逸和调试黑盒。
  2. 禁止走私高危指令 (ExecuteSystemTool):当前网关的 resolvePermission 审批流尚未在 TUI 侧完善,因此在 gateway 模式下显式阻断任何 AI 工具执行回调(抛出 unsupported_action),拒绝绕过网关走本地后门,死守安全底线。
  3. 最小化会话映射loadSession 采用最小可用字段映射策略,聚焦于打通打字机流式主链路,将 UI 私有状态与核心业务状态解耦。

✅ 验收标准 (Acceptance Criteria)

  • RPC 基座稳固:Token 静默读取、错误码精准映射、request_id 链路贯穿测试通过。
  • 流式数据解析安全:强类型解包与异常 Payload 容错测试通过。
  • 核心链路打通:使用 --runtime-mode=gateway 启动时,能够正常发起对话、接收大模型流式输出,且 metrics 指标 (gateway_requests_total) 正常增长。
  • 全量回归绿灯go test ./... 包含所有适配层拦截策略的单元测试全部通过。

pionxe added 2 commits April 20, 2026 16:54
完成 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 中增加并发创建导致的唯一约束冲突兜底,触发冲突时自动回读。
Copy link
Copy Markdown

@fennoai fennoai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

完成四轮审查(代码质量、性能、安全、文档)后,保留以下值得处理的问题:1 个高优先级可靠性/性能问题,1 个中优先级错误处理问题。

select {
case <-c.closed:
return
case c.notifications <- notification:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High · Reliability/Performance

这里向 c.notifications 的发送是阻塞的。readLoop 同时负责读取同一条 JSON-RPC 连接上的通知和响应;一旦通知通道被写满(例如 UI 短暂不消费事件),readLoop 会卡住,后续响应无法被分发到 pending,在途 RPC 会整体超时/假死。

建议把通知分发改成不会阻塞 readLoop 的策略(例如独立转发协程 + 有界队列/丢弃策略),至少保证响应分发不被通知背压拖住。

Comment thread internal/runtime/runtime.go Outdated
return true
}
normalized := strings.ToLower(strings.TrimSpace(err.Error()))
return strings.Contains(normalized, "not found") ||
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium · Error handling correctness

isRuntimeSessionNotFoundError 直接按字符串匹配 "not found" 过于宽泛,可能把非“会话不存在”的错误(例如 schema/依赖缺失类错误)误判为 not-found,然后进入 CreateSession 分支,掩盖真实故障。

建议优先使用明确的哨兵错误或 errors.Is(例如存储层定义的 not-found 错误、os.ErrNotExistsql.ErrNoRows 等)来做精确分类,避免误触发 upsert。

@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你帮我解决CI错误问题,并根据Review 指出了当前实现中的两个重要隐患,一个是并发阻塞问题,一个是错误处理不够严谨(上轮我们为了快速调通使用了宽泛的字符串匹配)。

请立即进行以下重构以满足生产级可靠性:

  1. 修复 readLoop 的阻塞问题 (High Priority)
    文件:internal/tui/services/gateway_rpc_client.go
    要求:readLoop 绝对不能在向 c.notifications 发送数据时被阻塞。请引入一个独立的通知分发机制(例如一个带缓冲的有界队列,或者一个独立的 dispatcher 协程)。如果 UI 消费缓慢导致缓冲满,请采取安全的丢弃策略(Drop with warning log),必须保证 readLoop 能够继续丝滑地处理其他正常的 RPC Response。

  2. 收紧 Upsert 错误判定条件 (Medium Priority)
    文件:internal/runtime/runtime.go (及相关的 Store 实现)
    要求:废弃 isRuntimeSessionNotFoundError 中宽泛的 "not found" 字符串匹配!

请在 internal/session (存储层) 中统一定义一个哨兵错误,例如 ErrSessionNotFound = errors.New("session not found")。

让 SQLite Store 在查不到数据时严格返回这个特定的错误,或者使用 sql.ErrNoRows / os.ErrNotExist 并包装它。

在 Runtime 层的 Upsert 判断中,只使用 errors.Is(err, store.ErrSessionNotFound) 或极其精确的底层错误断言,防止其他模块的缺失错误(如依赖找不到)误触发重建流程。

请完成这两项硬核改造,并确保所有的单元测试(尤其是并发测试和边界错误测试)全部通过!

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

已完成你要求的两项重构并推送到当前 PR 分支(pionxe:main),commit: 9e1101d

变更摘要

  1. 修复 readLoop 阻塞风险(internal/tui/services/gateway_rpc_client.go
  • 新增独立通知分发机制:notificationQueue(有界缓冲)+ dispatcher 协程。
  • readLoop 改为无阻塞入队:队列满时直接丢弃并 warning log,不再阻塞响应处理。
  • 关闭流程增强:Close() 中等待 dispatcher 退出,避免关闭时并发写通知通道。
  1. 收紧 Upsert 错误判定(internal/runtime/runtime.go + internal/session/*
  • internal/session/store.go 新增哨兵错误:ErrSessionNotFound
  • SQLite 存储层会话缺失路径统一返回包装错误:errors.Is(err, session.ErrSessionNotFound) 可精确命中(并保留 os.ErrNotExist 兼容链)。
  • runtime.CreateSession 的 not-found 判定改为仅认 errors.Is(err, session.ErrSessionNotFound),移除宽泛字符串匹配。

测试更新

  • 新增并发回归测试:TestGatewayRPCClientReadLoopDoesNotBlockOnNotifications,覆盖通知背压场景下 RPC 响应不被饿死。
  • 新增 runtime 边界测试:TestServiceCreateSessionReturnsOriginalErrorWhenMissingErrorIsNotSentinel,确保非哨兵错误不会误触发 upsert。
  • 更新 session 相关测试断言,验证 ErrSessionNotFound 与兼容错误链。

验证结果

  • go test ./... 全部通过。

View job run

…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>
@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你帮我解决CI/build-test失败问题!

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

处理结果

已定位并修复本次 CI/build-test 失败,修复提交已推送到当前 PR 头分支 pionxe:main8de1aae

根因

失败发生在 internal/gateway 的测试 TestNetworkServerSSELimitAndWriteErrorBranches。该测试在建立 WS 后立即发起 SSE 请求,存在时序竞争:WS 连接尚未完成服务端登记时,SSE 可能拿到可用名额并返回 200,导致断言期望 503 偶发失败。

修复

文件:internal/gateway/network_server_additional_test.go

  • 在发起 SSE 请求前增加显式等待:waitForWebSocketConnectionCount(t, server, 1, 2*time.Second)
  • 让断言建立在确定状态上,消除竞争导致的 flaky。

本地验证

  • go test ./internal/gateway -run TestNetworkServerSSELimitAndWriteErrorBranches -count=200
  • go test ./... -covermode=atomic -coverprofile=coverage.out
  • go build ./...

未执行项

  • 未直接操作 GitHub Actions 的“重新运行失败任务”按钮;请在 PR 页触发一次重跑以确认 CI 绿灯。

View job run

…tion

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你帮我解决codecov/patch的覆盖率低的问题,要将这覆盖率提升至百分之九十五!

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

完成情况

已完成针对 codecov/patch 的补测增强并推送到当前 PR 分支。

已执行

  • 新增/扩展测试覆盖以下模块分支与错误路径:
    • internal/tui/services: gateway_rpc_client / gateway_stream_client / remote_runtime_adapter
    • internal/app/bootstrap_test.go
    • internal/cli/gateway_runtime_bridge_test.go
    • internal/runtime/create_session_test.go
    • internal/session/sqlite_store_additional_test.go
  • 本地验证:go test ./... 全量通过。
  • 基于 origin/main...HEAD 的 patch 覆盖率本地估算:96.45% (1248/1294)

推送信息

说明

  • codecov/patch 的最终数值以 Codecov 平台计算为准;本地估算已达到并超过 95% 目标。

View job run

xgopilot and others added 3 commits April 20, 2026 13:15
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
@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你解决CI错误问题:undefined: newRemoteRuntimeAdapter

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

处理结果

已修复 CI 报错 undefined: newRemoteRuntimeAdapter

  • 修复内容:在 internal/app/bootstrap.go 的依赖注入变量区补回工厂函数变量绑定:newRemoteRuntimeAdapter = defaultNewRemoteRuntimeAdapter
  • 原因:BuildRuntime(..., RuntimeModeGateway) 中调用了 newRemoteRuntimeAdapter(...),但符号未声明,导致编译失败;同时测试也依赖该变量做替换注入。
  • 验证:
    • go test ./internal/app -count=1 通过
    • go test ./... 全量通过
  • 已推送提交到当前 PR 分支 pionxe:main8de41b4

未做事项:未引入额外重构或行为变更,仅做最小修复以恢复 CI。

View job run

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
@phantom5099
Copy link
Copy Markdown
Collaborator

@xgopilot 给我一个能给AI用来review的提示词,先确认当前总体方案设计,再确认有没有存在风险点(诸如方案设计、模块边界等等)

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

可直接使用的 Review 提示词

你现在是该仓库的资深架构审查员,请审查 PR #374feat(tui): [EPIC-INT-01B] 引入 RemoteRuntimeAdapter,实现 TUI 客户端向 Gateway 平滑下沉)。

请严格按以下顺序输出:

  1. 先做“总体方案设计确认”
  • 判断该 PR 是否真正满足目标:在保持 local 默认体验不变前提下,引入 gateway 模式并打通 TUI -> Runtime -> Gateway
  • 判断是否遵守仓库架构边界:禁止跨层直连、禁止 provider 差异泄漏到 runtime/tui、工具执行逻辑不得绕过 internal/tools
  • 判断关键决策是否一致且可落地:Fail-Fast、禁止高危本地后门执行、最小会话映射。
  • 给出结论:通过 / 有条件通过 / 不通过,并说明依据。
  1. 再做“风险点审查”
    请仅列问题,不写表扬。按严重级别排序:Critical > High > Medium > Low
    每个问题使用固定格式:
  • 标题
  • 级别
  • 位置(文件路径 + 行号)
  • 现象与证据
  • 影响(功能/安全/稳定性/可维护性)
  • 修复建议(可执行)
  1. 风险覆盖面至少包含
  • 方案设计风险:握手链路 authenticate -> bindStream -> loadSession -> run 是否有竞态、重试风暴、状态不一致。
  • 模块边界风险:tui/runtime/provider/tools/config/session 是否出现职责越界或耦合回退。
  • 安全风险:gateway 模式下是否仍存在本地工具绕过、token 加载与传递是否泄露、错误映射是否暴露敏感信息。
  • 稳定性风险:流式事件弱类型反序列化是否会 panic/丢事件/错型;超时与重试是否导致重复提交。
  • 回归风险:--runtime-mode 默认值、local/gateway 切换、旧链路兼容、失败时用户可观测性。
  • 测试充分性:是否覆盖停止条件、错误事件派发、tool result 回灌、异常 payload 容错、配置校验与序列化兼容。
  1. 输出要求
  • 先给“总体方案结论”,再给“问题清单”。
  • 若无问题,明确写:未发现阻断性问题,并列出“剩余测试盲区/观察项”。
  • 不要泛泛而谈,所有判断必须绑定具体代码位置。

使用建议

如果你希望它更“严格”,可在开头再加一句:
以阻止潜在线上事故为优先目标,宁可误报轻微风险,也不要漏报边界破坏和安全后门。

View job run

@phantom5099
Copy link
Copy Markdown
Collaborator

@xgopilot 你现在是该仓库的资深架构审查员,以阻止潜在线上事故为优先目标,宁可误报轻微风险,也不要漏报边界破坏和安全后门。请审查 PR #374feat(tui): [EPIC-INT-01B] 引入 RemoteRuntimeAdapter,实现 TUI 客户端向 Gateway 平滑下沉)。

请严格按以下顺序输出:

  1. 先做“总体方案设计确认”
  • 判断该 PR 是否真正满足目标:在保持 local 默认体验不变前提下,引入 gateway 模式并打通 TUI -> Runtime -> Gateway
  • 判断是否遵守仓库架构边界:禁止跨层直连、禁止 provider 差异泄漏到 runtime/tui、工具执行逻辑不得绕过 internal/tools
  • 判断关键决策是否一致且可落地:Fail-Fast、禁止高危本地后门执行、最小会话映射。
  • 给出结论:通过 / 有条件通过 / 不通过,并说明依据。
  1. 再做“风险点审查”
    请仅列问题,不写表扬。按严重级别排序:Critical > High > Medium > Low
    每个问题使用固定格式:
  • 标题
  • 级别
  • 位置(文件路径 + 行号)
  • 现象与证据
  • 影响(功能/安全/稳定性/可维护性)
  • 修复建议(可执行)
  1. 风险覆盖面至少包含
  • 方案设计风险:握手链路 authenticate -> bindStream -> loadSession -> run 是否有竞态、重试风暴、状态不一致。
  • 模块边界风险:tui/runtime/provider/tools/config/session 是否出现职责越界或耦合回退。
  • 安全风险:gateway 模式下是否仍存在本地工具绕过、token 加载与传递是否泄露、错误映射是否暴露敏感信息。
  • 稳定性风险:流式事件弱类型反序列化是否会 panic/丢事件/错型;超时与重试是否导致重复提交。
  • 回归风险:--runtime-mode 默认值、local/gateway 切换、旧链路兼容、失败时用户可观测性。
  • 测试充分性:是否覆盖停止条件、错误事件派发、tool result 回灌、异常 payload 容错、配置校验与序列化兼容。
  1. 输出要求
  • 先给“总体方案结论”,再给“问题清单”。
  • 若无问题,明确写:未发现阻断性问题,并列出“剩余测试盲区/观察项”。
  • 不要泛泛而谈,所有判断必须绑定具体代码位置。

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

总体方案结论

结论:不通过

依据(按你要求的三项核对):

  1. --runtime-mode 默认仍是 localgateway 路径已接入,但关键链路存在阻断级稳定性风险(事件丢失、重试策略失效),无法判定“平滑下沉”已达标。
  2. 分层上未发现明显 provider 协议向 runtime/tui 泄漏,但 gateway 客户端事件/会话错误处理存在跨层语义失真(误判 not found 并隐式建会话)。
  3. 三个关键决策里,Fail-Fast 与“禁止本地高危后门执行”已落地;但握手实现顺序与声明不一致,且存在状态一致性风险。

问题清单

标题:Gateway 通知队列满时静默丢事件,可能导致流式输出/完成事件缺失
级别:Critical
位置:internal/tui/services/gateway_rpc_client.go:416internal/tui/services/gateway_rpc_client.go:423
现象与证据:enqueueNotificationnotificationQueue 满时直接走 default 分支丢弃通知,仅打印日志 drop notification ... queue_full
影响:稳定性。gateway.event 被丢弃后,TUI 可能缺少 chunk/tool_result/done/error 关键事件,出现输出残缺、状态卡死、取消状态错误等线上事故。
修复建议:gateway.event 不能静默丢弃。建议改为“阻塞投递 + 可取消上下文 + 上限告警/断线重连”,或至少区分关键通知类型,关键类型必须强保证送达。

标题:RemoteRuntimeAdapter 默认重试被意外关闭,握手抗抖动能力与设计目标不一致
级别:High
位置:internal/tui/services/remote_runtime_adapter.go:79internal/tui/services/remote_runtime_adapter.go:86internal/tui/services/remote_runtime_adapter.go:107internal/tui/services/gateway_rpc_client.go:211
现象与证据:适配器把 options.RetryCount 原样存入 retryCount;默认值为 0。后续 callFrameRetries: r.retryCount,而 GatewayRPCClient.CallWithOptions 仅在 <0 时回落默认重试,因此 0 会变成“0 次重试”。
影响:稳定性/回归。gateway 模式下默认握手(loadSession/bindStream/compact/...)在瞬时抖动时直接失败,违背 PR 声明中的“默认重试机制”。
修复建议:在 RemoteRuntimeAdapter 构造时统一归一化 retryCount<=0 为默认值,或传递 Retries:-1 让 RPC 客户端使用自身默认;补充“默认配置下确实发生重试”的单测。

标题:握手顺序与方案声明不一致,存在先期事件丢失窗口
级别:High
位置:internal/tui/services/remote_runtime_adapter.go:116internal/tui/services/remote_runtime_adapter.go:130internal/tui/services/remote_runtime_adapter.go:133internal/tui/services/remote_runtime_adapter_test.go:64
现象与证据:当前 Submit 实际顺序是 authenticate -> loadSession -> bindStream -> run,测试也固定该顺序;与你要求核对的目标序列 authenticate -> bindStream -> loadSession -> run 不一致。
影响:方案设计/状态一致性。若 loadSession 侧后续引入事件输出或状态变更,bindStream 之前的窗口会丢失事件,导致客户端状态与网关状态不一致。
修复建议:统一协议顺序并固化契约测试(顺序断言 + 乱序失败用例);若必须先 loadSession,需补充协议层保证“loadSession 无流式副作用”。

标题:流解析错误事件会无条件清空活跃 run,导致取消与状态跟踪失真
级别:Medium
位置:internal/tui/services/gateway_stream_client.go:77internal/tui/services/remote_runtime_adapter.go:479internal/tui/services/remote_runtime_adapter.go:495
现象与证据:流解析失败时发出 EventError,但不带 run_id/session_idobserveEventEventErrorclearActiveRun(runID),而 clearActiveRunrunID=="" 时会直接清空当前活跃 run。
影响:稳定性。任意坏包/错型包都可能把当前有效 run 清掉,后续 CancelActiveRun 失效,UI 与真实执行状态漂移。
修复建议:仅在 runID 非空且匹配当前活跃 run 时清理;解析失败事件尽量携带源 frame 的 run_id/session_id,并区分“流解码错误”和“业务运行错误”。

标题:LoadSession 的 not-found 判断过宽,可能误判并错误创建新会话
级别:Medium
位置:internal/cli/gateway_runtime_bridge.go:192internal/cli/gateway_runtime_bridge.go:417
现象与证据:isRuntimeNotFoundError 通过字符串包含("not found"/"no such file" 等)判定,再触发 CreateSession。这会把非 not-found 错误(但文案包含相关字样)误判为不存在。
影响:功能正确性/可维护性。会掩盖真实错误并产生意外新会话,导致排障困难和状态污染。
修复建议:改为哨兵错误判定(errors.Is(err, session.ErrSessionNotFound) / os.ErrNotExist),仅在精确命中时走自动创建分支,其他错误原样上抛。

测试盲区/观察项

  1. 当前测试未覆盖“通知队列溢出时关键事件不丢失”的契约。
  2. 当前测试未覆盖“RemoteRuntimeAdapter 默认参数下的重试行为”(只覆盖了显式 retryCount=1)。
  3. 当前测试未覆盖“流解析错误不应影响其他活跃 run 状态”的回归场景。

已本地执行:go test ./...(通过),但以上问题属于实现策略风险,不会被现有用例拦截。

View job run

@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你解决下述review的问题!
请你立刻集中精力,一次性彻底修复以下 5 个核心问题以及 3 个测试盲区:

  1. 严禁静默丢弃关键事件 (Critical)
    位置:internal/tui/services/gateway_rpc_client.go
    要求:在 enqueueNotification 中,当 notificationQueue 满时直接走 default 丢弃是绝对不可接受的。请修改分发策略:

改为阻塞投递(结合 Context 超时防死锁),或者引入一个弹性的、能保证关键事件(如 chunk/done/error/tool_result)绝对必达的队列分发机制。

无论如何,绝不允许丢弃 gateway.event 导致客户端状态卡死。

  1. 修复适配器默认重试失效 (High)
    位置:internal/tui/services/remote_runtime_adapter.go
    要求:当前 RetryCount 默认传 0 导致了“0 次重试”。请在 RemoteRuntimeAdapter 的构造函数中做归一化校验:如果传入的 options.RetryCount <= 0,必须强制回退为系统的默认重试次数(如 defaultGatewayRPCRetryCount 或传 -1 让底层处理)。务必保证网络抖动时的兜底能力。

  2. 严格对齐握手时序契约 (High)
    位置:internal/tui/services/remote_runtime_adapter.go
    要求:目前的 Submit 时序是错的(auth -> load -> bind -> run)。

必须严格修改为:authenticate -> bindStream -> loadSession -> run。

必须先建好流式接收通道(bindStream),再去请求会话(loadSession),防止后续业务产生副作用时事件丢失。并同步修正固定该顺序的断言测试。

  1. 修复流解析错误导致活跃 Run 被误杀 (Medium)
    位置:internal/tui/services/gateway_stream_client.go & remote_runtime_adapter.go
    要求:

在 clearActiveRun(runID) 中增加严格校验:仅当传入的 runID 非空,且真正匹配当前界面的活跃 run 时,才允许清理状态。

防止流解析产生的无 ID EventError 盲目触发清理,导致后续真正的 Cancel 操作失效。

  1. 彻底收紧 LoadSession 的 Not-Found 判定 (Medium)
    位置:internal/cli/gateway_runtime_bridge.go
    要求:彻底废弃 isRuntimeNotFoundError 中宽泛的字符串包含匹配("not found" 等)。

必须改用标准的哨兵错误断言,例如严格的 errors.Is(err, session.ErrSessionNotFound) 或 errors.Is(err, os.ErrNotExist)。

只有在精确命中哨兵错误时,才允许走自动创建(Upsert)分支,掩盖真实底层故障是零容忍的。

  1. 填补 3 个关键测试盲区
    要求:根据 Review,补充以下 3 个高价值回归用例:

“通知队列满/高并发场景下,流式关键事件保证不丢失”的契约测试。

“RemoteRuntimeAdapter 默认零值参数下,确实发生重试”的行为覆盖。

“触发一个无 ID 的流解析错误,断言活跃的 Run 状态未被意外清空”的防回归测试。

请严格按照上述要求逐一修复

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

修复结果

已按要求一次性完成 5 个核心问题和 3 个测试盲区修复,并已推送到当前 PR 分支 pionxe:main(commit: 6b68f48)。

已完成项

  1. internal/tui/services/gateway_rpc_client.go
  • enqueueNotification 改为阻塞投递(监听 c.closed),移除队列满时 default 分支丢弃逻辑,禁止静默丢事件。
  1. internal/tui/services/remote_runtime_adapter.go
  • 新增 normalizeRemoteRuntimeRetryCount,对 RetryCount<=0 统一归一化为默认重试次数。
  • 构造函数与内部装配均使用归一化值,避免零值导致默认重试失效。
  • Submit 握手顺序修正为:authenticate -> bindStream -> loadSession -> run
  • clearActiveRun(runID) 收紧:仅当 runID 非空且与当前 active run 严格匹配时才清理。
  1. internal/cli/gateway_runtime_bridge.go
  • isRuntimeNotFoundError 改为仅接受哨兵错误:errors.Is(err, session.ErrSessionNotFound)errors.Is(err, os.ErrNotExist)
  • 删除宽泛字符串包含匹配,避免掩盖真实底层故障。

新增/调整回归测试

  1. 通知队列满/高并发关键事件不丢
  • internal/tui/services/gateway_rpc_client_additional_test.go
  • 新增 TestGatewayRPCClientEnqueueNotificationDoesNotDropUnderQueuePressure
  1. 默认零值参数下重试归一化生效
  • internal/tui/services/remote_runtime_adapter_additional_test.go
  • 新增 TestNewRemoteRuntimeAdapterWithClientsNormalizesRetryCount
  • 新增 TestRemoteRuntimeAdapterUsesDefaultRetryWhenOptionsZero
  • 同时在 TestNewRemoteRuntimeAdapterBranches 增加零值重试断言。
  1. 无 ID 流解析错误不清空活跃 Run
  • internal/tui/services/remote_runtime_adapter_additional_test.go
  • TestRemoteRuntimeAdapterEventObservationAndActiveRunState 增加 EventErrorRunID 场景断言,验证 active run 保持不变。
  1. 同步修正握手顺序相关断言
  • internal/tui/services/remote_runtime_adapter_test.go 与部分 additional 测试按新时序更新。
  1. 同步修正 not-found 收紧后的桥接测试
  • internal/cli/gateway_runtime_bridge_test.go 改为哨兵错误驱动分支,新增“字符串 not found 不触发 upsert”回归。

验证

  • 已执行:go test ./...(通过)

View job run

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: pionxe <148670367+pionxe@users.noreply.github.com>
@phantom5099
Copy link
Copy Markdown
Collaborator

@xgopilot 再做一轮审查性review,要求你谨慎分析

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

总体方案结论

结论:有条件通过。当前实现已覆盖 gateway 模式主链路,但仍有阻断级稳定性风险,需要先修复后再合并。

问题清单

标题:关闭流程存在并发 panic 风险(send on closed channel
级别:Critical
位置:internal/tui/services/gateway_rpc_client.go:235internal/tui/services/gateway_rpc_client.go:416-421internal/tui/services/gateway_rpc_client.go:355-376
现象与证据:Close()close(c.notificationQueue)readLoop 并未先停止,仍可能并发执行 enqueueNotificationenqueueNotification 里有 case c.notificationQueue <- notification,当队列已关闭时存在直接 panic 风险。
影响:稳定性。进程在关闭/重连窗口可能直接崩溃。
修复建议:不要在 Close() 直接关闭 notificationQueue;先确保 readLoop 退出(例如 readLoop WaitGroup),再安全回收队列。或仅通过 c.closed 让 dispatcher/readLoop 退出,不关闭队列。

标题:readLoop 在通知背压下仍可能被无限阻塞,导致 RPC 响应饥饿
级别:High
位置:internal/tui/services/gateway_rpc_client.go:355-376internal/tui/services/gateway_rpc_client.go:390-421
现象与证据:enqueueNotification 为阻塞发送,且无超时/熔断;当 notifications 下游不消费时,dispatcher 卡住,notificationQueue 被填满后 readLoop 阻塞,后续 response 无法继续 decode/dispatch。
影响:功能正确性/稳定性。控制面请求(authenticate/loadSession/run 等)会无故超时,表现为网关模式“卡死”。
修复建议:为通知背压增加 fail-fast 机制(如投递超时后主动断连重建,或关键/非关键事件分级队列并保证关键事件必达),确保 readLoop 永不无限期阻塞。

标题:CreateSession 并发冲突判定仍依赖宽泛字符串匹配
级别:Medium
位置:internal/runtime/runtime.go:309-317
现象与证据:isRuntimeSessionAlreadyExistsError 使用 "already exists"/"unique constraint"/"duplicate" 字符串匹配判定并发创建冲突。
影响:错误分类不精确。其他非冲突错误若包含这些片段会被误判,进而触发 LoadSession 回退,掩盖真实故障。
修复建议:在 internal/session 定义 ErrSessionAlreadyExists(或返回可断言的底层错误),runtime 层改为 errors.Is 精确判定。

额外测试盲区

  1. 缺少“Close()readLoop/enqueueNotification 并发交错”防 panic 回归测试。
  2. 缺少“下游长期不消费通知时,RPC 响应仍可前进或可观测 fail-fast”的契约测试。

已执行:go test ./...(通过)。本次为审查反馈,未做代码修改。

View job run

@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你根据review的问题进行修复

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

修复完成

已根据最新一轮 review 问题完成修复并推送。

变更摘要

  • 修复 GatewayRPCClient 关闭并发风险:Close() 不再关闭 notificationQueue,避免 readLoop/enqueue 并发发送到已关闭通道导致 panic。
  • 增加通知背压 fail-fast:enqueueNotification 增加有界等待超时(默认 3s),持续背压时强制断连并返回,避免 readLoop 无限阻塞。
  • 收紧 runtime 并发创建冲突判定:isRuntimeSessionAlreadyExistsError 改为 errors.Is(err, session.ErrSessionAlreadyExists) / errors.Is(err, os.ErrExist)
  • internal/session 增加哨兵错误 ErrSessionAlreadyExists
  • SQLite CreateSession 在主键/唯一约束冲突时统一映射为 ErrSessionAlreadyExists(并保留 os.ErrExist 错误链)。

新增/调整测试

  • internal/tui/services/gateway_rpc_client_additional_test.go
  • internal/runtime/create_session_test.go
  • internal/session/store_test.go

覆盖了:

  • 关闭信号下 dispatcher 正常退出;
  • 通知背压触发 readLoop fail-fast;
  • 关闭过程中的 enqueue 不会卡死;
  • runtime 只接受哨兵 already-exists 错误;
  • SQLite duplicate create 返回 ErrSessionAlreadyExists

验证

  • go test ./... 通过。

提交与推送

未执行项

  • 未直接操作 GitHub Actions 页面重跑;请在 PR 页面触发 CI 检查。

View job run

…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
@pionxe
Copy link
Copy Markdown
Collaborator Author

pionxe commented Apr 20, 2026

@xgopilot 请你再度对该PR进行审查性review

@fennoai
Copy link
Copy Markdown

fennoai Bot commented Apr 20, 2026

总体方案结论

结论:有条件通过

本轮基于 origin/main...HEAD 复审后,gateway 模式主链路(authenticate -> bindStream -> loadSession -> run)与默认 local 行为保持并存,之前指出的 5 个核心问题已在当前代码中落地修复,未再发现新的阻断性缺陷。

问题清单

未发现新的阻断性问题。

剩余测试盲区/观察项

  1. GatewayRPCClient 背压策略当前是“超时后强制断连”,建议继续观察真实高并发下的重连抖动与用户侧感知(是否出现短时事件中断)。
  2. 建议补一组跨组件长稳压测(持续 run + 高频 event + 主动 cancel + reconnect)以验证网关端到端恢复能力,而不仅是单元级行为。

本地核验

  • 已执行:go test ./...(通过)
  • 本次仅做审查性 review,未做代码修改。

View job run

@phantom5099 phantom5099 merged commit 7bf80f3 into 1024XEngineer:main Apr 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EPIC-INT-01B] 阶段 2:TUI 通信层下沉适配 (Client Migration - Phase 1)

3 participants