Files
qiming/qimingclaw/crates/agent-electron-client/docs/review-reg-and-autoreconnect.md

153 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Review: reg 与自动重连逻辑
**审查范围**App.tsx 自动重连、auth.ts syncConfigToServer、processHandlers.ts Lanproxy 启动
**结论**:逻辑正确,注释与行为一致;有少量可改进点。
---
## 1. 已做对的部分
### 1.1 先 reg 成功再启动服务
- **savedKey 路径**`syncConfigToServer()``await`,只有 `result` 为真时才调用 `startServicesSequentially()`,不会在 reg 失败时启动服务。
- **配置来源**`syncConfigToServer` 内用本次 reg 的 `response.serverHost` / `response.serverPort` 调用 `saveServerConfig()`lanproxy 启动时从 settings 读到的已是本次 reg 写入的最新值。
### 1.2 reg 返回可能变化的处理
- `syncConfigToServer` 在 reg 成功后将本次返回的 `serverHost`/`serverPort` 写回配置,并返回 response调用方在 reg 成功后再启动,符合「用本次最新结果」的语义。
- 注释在 App.tsx 与 auth.ts 中已说明「reg 返回可能会变化,先写配置再启动」。
### 1.3 失败与提示
- reg 失败(`result` 为 null时只打日志 + notification不启动服务行为与注释一致。
- 异常被 try/catch 捕获,避免未处理 Promise rejection。
### 1.4 代码质量
- 当前修改未引入新的 lint 报错。
- 类型与现有风格一致(仅个别 `as any` 为既有写法)。
---
## 2. 可改进点(非必须)
### 2.1 「向导刚完成」路径未再调 reg
- **现状**`setupJustCompleted.current === true` 时直接 `startServicesSequentially(...)`,不调用 `syncConfigToServer`
- **原因**:向导完成时通常刚执行过登录,`loginAndRegister` 已调过 reg 并执行 `saveServerConfig`,配置已是最新。
- **建议**:若希望与「重新打开客户端」完全统一(始终先 reg 再启动),可在该分支也先 `await syncConfigToServer({ suppressToast: true })`,成功后再 `startServicesSequentially`;否则保持现状即可,在注释中注明「向导刚完成时假定登录流程已执行 reg直接使用当前配置启动」。
### 2.2 自动重连中的 catch 未对用户提示
- **现状**`autoReconnect``catch``console.error('[App] 自动重连失败:', error)`,用户无 toast。
- **建议**:若希望网络异常等未预期错误也有提示,可在 catch 里补一次 `notification.warning`(与 `result` 为 null 时的文案区分开,例如「自动重连异常,请稍后重试或手动启动服务」),避免重复可复用现有「自动重连失败」的 key 或 message。
### 2.3 类型收紧(低优先级)
- `startServicesSequentially``agentConfig``lpConfig` 使用 `as any`,后续可改为具体类型(如从 shared 或 api 类型中抽取),便于后续扩展 reg 返回字段时的类型安全。
---
## 3. 与 processHandlers 的衔接
- Lanproxy 启动仍从主进程 `readSetting('lanproxy_config')` / `lanproxy.server_host` / `lanproxy.server_port` 读配置;这些值由渲染进程侧 `saveServerConfig` 经 IPC 写入顺序为「reg 成功 → syncConfigToServer 写配置 → 再启动服务」,因此主进程读到的已是本次 reg 写入的最新配置,无需改 processHandlers。
---
## 4. 已修复:账号切换后 lanproxy 使用旧 clientKey2026-03-12
### 问题
**场景一:不退出直接切换账号**
`handleLogin``handleStartService('lanproxy')` 时传入新账号的 `clientKey`,但主进程 `startLanproxyProcess` 检测到 `ctx.lanproxy.running === true`(旧进程仍在),直接返回 `success: true` 而不重启 → 旧 key 继续使用 → 服务端认为是旧账号 → 新账号会话显示「客户端离线」。
**场景二:退出登录后用新账号登录**
`handleLogout` 会停止所有服务agent/fileServer/lanproxy/mcpProxy/computerServer并清除 auth 信息。用户用新账号登录时 `handleLogin` 执行 `loginAndRegister` → 写入新 configKey/savedKey → 启动服务。由于 logout 已停掉所有服务lanproxy 的 `ctx.lanproxy.running === false`,新进程用新 `clientKey` 正常启动。此场景在修复前已能正常工作。
### 修复
`processHandlers.ts``startLanproxyProcess` 中,将「已运行则跳过」改为「已运行则异步停止(等待进程退出)再用本次传入的 config 重启」:
```typescript
if (ctx.lanproxy.running) {
// 切换账号后 clientKey 会变化,必须用新配置重启,不能跳过。
// 否则旧进程继续使用旧 clientKey 导致「本地显示已联通、会话显示离线」。
log.info('[Lanproxy] 已在运行,先停止再用新配置重启');
await ctx.lanproxy.stopAsync();
}
```
`stopAsync()` 发送 SIGTERM 并等待进程 `exit` 事件5s 超时后 SIGKILL确保端口/资源释放后再启动新进程。
**修改点**
- `src/main/processManager.ts`:新增 `stopAsync()` 方法(含 stdio 监听器清理,防止 Windows 句柄泄漏)
- `src/main/ipc/processHandlers.ts``startLanproxyProcess` 内的 running 守卫改用 `await stopAsync()`
| 场景 | 修复前 | 修复后 |
|------|--------|--------|
| 不退出直接切换 | ❌ lanproxy 用旧 key新账号离线 | ✅ 先停旧进程再用新 config 启动 |
| 退出后用新账号登录 | ✅ logout 已停服务,正常启动 | ✅ 未运行,直接启动 |
| 任何路径调用 lanproxy:start | ❌ 已运行时跳过 | ✅ 始终应用本次传入的配置 |
---
## 5. 总结
| 项目 | 状态 |
|----------------|------|
| reg 成功后才启动服务 | 正确 |
| 使用本次 reg 返回的配置 | 正确(通过 saveServerConfig 写再读) |
| 注释与行为一致 | 是 |
| 失败分支与提示 | 正确catch 可考虑补用户提示 |
| 向导完成路径 | 合理,若需统一可先 reg 再启动 |
| 账号切换时先停旧服务 | 已修复2026-03-12 |
| 手动启动单个服务时先 reg | 已补充2026-03-12 |
**结论**:当前实现满足「等待 reg 成功返回后才启动服务」且「使用 reg 可能变化后的最新配置」;账号切换场景已通过先停后启修复;手动启动单个服务场景已补充先 reg 再启动。
---
## 6. 手动启动单个服务时补充 reg 调用2026-03-12
### 问题
**场景**:用户在 ClientPage 点击某个服务的「启动」按钮(非「启动全部」、非登录流程)。
此前 `handleStartService(svc.key)` 直接启动目标服务,**不调用 reg**。这意味着:
- 如果后端 `serverHost`/`serverPort` 发生过变化如服务端重新分配端口lanproxy 启动时读到的是旧值。
- 与「登录」和「启动全部」行为不一致——它们都在启动服务前/后调用 `syncConfigToServer()`
### 修复
新增 `handleStartServiceManual` 包装函数UI 按钮 onClick 改为调用此函数:
```typescript
const handleStartServiceManual = async (key: string) => {
// 先 reg确保 lanproxy 等服务启动时使用最新的后端返回数据
try {
await syncConfigToServer({ suppressToast: true });
} catch (e) {
console.error('[ClientPage] 手动启动服务前 reg 同步失败:', e);
}
await handleStartService(key);
onAuthChange?.();
};
```
### 时序对比
| 场景 | reg 时机 | 启动时机 |
|------|----------|----------|
| 登录流程 (`handleLogin`) | `loginAndRegister()` + 启动非代理服务后 `syncConfigToServer()` | reg 后启动 lanproxy |
| 启动全部 (`handleStartAll`) | 启动非代理服务后 `syncConfigToServer()` | reg 后启动 lanproxy |
| **手动启动单个** (`handleStartServiceManual`) | **先 `syncConfigToServer()`** | **reg 后启动目标服务** |
三个场景均保证 **reg 在服务启动之前完成**lanproxy 读到的 `serverIp`/`serverPort` 为 reg 返回的最新值。
### 修改点
- `src/renderer/components/pages/ClientPage.tsx`:新增 `handleStartServiceManual`,单个服务「启动」按钮 onClick 改用此函数