fix: migrate legacy native chat runtime configs to React#267
fix: migrate legacy native chat runtime configs to React#267RachelForster wants to merge 1 commit into
Conversation
Reviewer's Guide by Ameath小爱整理了一条便于跟进的核对思路。 运行时决定点位于 文件级变更
关联 Issue
Tips and commands
Original review guide in EnglishThe runtime decision point is |
There was a problem hiding this comment.
小爱先从需要留意的细节开始看。
小爱已完成全部变更审查,共发布 4 条行级建议(高风险 0,中风险 3)。
Prompt for AI Agents
请逐项处理以下审查问题;修改前先核实上下文,修改后运行相关测试。
1. 将活动历史与分支树作为同一崩溃一致提交保存 / Commit active history and branch tree atomically
frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>在 fork 或 switch 后,_persist_branch_state() 依次覆盖 active.json、删除 active.json.tmp、再覆盖 branches.json,三者没有同一提交点。若进程在活动历史写入后、分支树写入前崩溃,磁盘历史已属于新分支 B,而树仍将旧分支 A 标为活动;恢复时 reconcile_active_branch_state() 会把 B 的内容写入 A,破坏分支身份而不只是丢失最后消息。开启现有 fork/tree 实验功能的 legacy native 用户会因本行开始可达此路径。应使用共享 generation 的原子 manifest 提交,或在恢复时验证活动历史与分支树身份。
After fork/switch, _persist_branch_state() overwrites active.json, removes active.json.tmp, and only then overwrites branches.json, without a common commit point. A crash between writes leaves B's history while A is still active in the tree; recovery assigns B's contents to A. Legacy native users with the existing fork/tree experimental controls enabled newly reach this path. Commit a shared generation atomically or validate branch identity during recovery.</issue_to_address>
2. 保留项目外自定义初始立绘的加载路径 / Preserve external custom initial sprites
frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>启动器会保留未知的 initialSpritePath,initial_sprite_path_for_characters() 也会将它传入运行时;原生 UIUpdateManager 会直接加载该绝对路径。此处强制 legacy native 配置走流式运行时后,StreamingUIUpdateManager 会把路径转成 /api/media,该端点通过 safe_project_path() 拒绝项目根目录以外的绝对路径。因此此前可在原生窗口显示的 D:/... 或 /home/... 自定义初始立绘会在 React Stage 中加载失败。迁移时应导入此类资产到受控目录,或让流式媒体通道安全地提供已选择的文件。
The launcher retains unknown initialSpritePath values and the native UI loads those absolute paths directly. Forcing a legacy native configuration into streaming turns it into /api/media, which safe_project_path() rejects outside the project root. Existing external custom initial sprites therefore no longer load in React Stage; import them into controlled storage during migration or serve selected files safely.</issue_to_address>
3. 使旁白对话的快照与实时事件一致 / Keep narrator snapshots consistent with live events
frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>SystemMiscUiHandler 会以非空 speaker 和 isSystem=True 发出具名 NARR/旁白;实时 React reducer 将其渲染为具名对话。但 fold_event_into_snapshot() 会对所有系统事件清空 characterName。ChatStreamService 将该快照用于重连、轮询恢复及稍后打开的 Chat Stage,因此这些路径会把同一条旁白显示为没有名称栏的对话。此次强制迁移使此前 native 用户首次受该流式快照不一致影响;折叠规则应仅在 speaker 为空时清空名称。
SystemMiscUiHandler emits named NARR/narrator events with a nonempty speaker and isSystem=True, and the live React reducer renders them as named dialogs. However, fold_event_into_snapshot() clears characterName for every system event. Reconnect, polling recovery, and later-opened Chat Stage clients therefore hydrate the same narrator without its name. Preserve nonempty speakers while folding snapshots.</issue_to_address>
4. 关闭 React 聊天窗时不要吞掉运行时关闭失败 / Do not swallow React runtime close failures
frontend_bridge_core/chat.py:93 (RIGHT)
<issue_to_address>Tauri 关闭路径会吞掉 closeRuntime() 的拒绝后继续执行 desktop_chat_window_destroy();该命令仅调用 window.destroy(),并未像 desktop_window_close 一样显式请求 bridge 关闭会话。因此当 /api/chat/close 在请求送达前失败时,聊天窗会消失,但 main.py 子进程和流式会话仍可能运行并阻止下一次启动。本行使 legacy native 配置首次进入这条 React 桌面关闭链路;应在销毁前保留窗口并报告失败,或让 Rust destroy 命令可靠地请求 bridge 关闭。
The Tauri close path swallows a closeRuntime() rejection and still calls desktop_chat_window_destroy(). That command only invokes window.destroy() and does not explicitly request bridge shutdown like desktop_window_close. If /api/chat/close fails before delivery, the window can disappear while the main.py process and stream session remain running and block the next launch. Preserve the window on failure or make the Rust destroy command reliably request bridge shutdown.</issue_to_address>
| return "native" if mode == "native" else "react" | ||
| def _chat_runtime_mode(_state: BridgeState) -> str: | ||
| """Return the only supported chat UI runtime.""" | ||
| return "react" |
There was a problem hiding this comment.
[MEDIUM · correctness] 将活动历史与分支树作为同一崩溃一致提交保存 / Commit active history and branch tree atomically
在 fork 或 switch 后,_persist_branch_state() 依次覆盖 active.json、删除 active.json.tmp、再覆盖 branches.json,三者没有同一提交点。若进程在活动历史写入后、分支树写入前崩溃,磁盘历史已属于新分支 B,而树仍将旧分支 A 标为活动;恢复时 reconcile_active_branch_state() 会把 B 的内容写入 A,破坏分支身份而不只是丢失最后消息。开启现有 fork/tree 实验功能的 legacy native 用户会因本行开始可达此路径。应使用共享 generation 的原子 manifest 提交,或在恢复时验证活动历史与分支树身份。
Original in English
After fork/switch, _persist_branch_state() overwrites active.json, removes active.json.tmp, and only then overwrites branches.json, without a common commit point. A crash between writes leaves B's history while A is still active in the tree; recovery assigns B's contents to A. Legacy native users with the existing fork/tree experimental controls enabled newly reach this path. Commit a shared generation atomically or validate branch identity during recovery.
置信度 97% · 可直接回复讨论 · 👍/👎 会影响后续审查
| return "native" if mode == "native" else "react" | ||
| def _chat_runtime_mode(_state: BridgeState) -> str: | ||
| """Return the only supported chat UI runtime.""" | ||
| return "react" |
There was a problem hiding this comment.
[MEDIUM · correctness] 保留项目外自定义初始立绘的加载路径 / Preserve external custom initial sprites
启动器会保留未知的 initialSpritePath,initial_sprite_path_for_characters() 也会将它传入运行时;原生 UIUpdateManager 会直接加载该绝对路径。此处强制 legacy native 配置走流式运行时后,StreamingUIUpdateManager 会把路径转成 /api/media,该端点通过 safe_project_path() 拒绝项目根目录以外的绝对路径。因此此前可在原生窗口显示的 D:/... 或 /home/... 自定义初始立绘会在 React Stage 中加载失败。迁移时应导入此类资产到受控目录,或让流式媒体通道安全地提供已选择的文件。
Original in English
The launcher retains unknown initialSpritePath values and the native UI loads those absolute paths directly. Forcing a legacy native configuration into streaming turns it into /api/media, which safe_project_path() rejects outside the project root. Existing external custom initial sprites therefore no longer load in React Stage; import them into controlled storage during migration or serve selected files safely.
置信度 96% · 可直接回复讨论 · 👍/👎 会影响后续审查
| return "native" if mode == "native" else "react" | ||
| def _chat_runtime_mode(_state: BridgeState) -> str: | ||
| """Return the only supported chat UI runtime.""" | ||
| return "react" |
There was a problem hiding this comment.
[MEDIUM · correctness] 使旁白对话的快照与实时事件一致 / Keep narrator snapshots consistent with live events
SystemMiscUiHandler 会以非空 speaker 和 isSystem=True 发出具名 NARR/旁白;实时 React reducer 将其渲染为具名对话。但 fold_event_into_snapshot() 会对所有系统事件清空 characterName。ChatStreamService 将该快照用于重连、轮询恢复及稍后打开的 Chat Stage,因此这些路径会把同一条旁白显示为没有名称栏的对话。此次强制迁移使此前 native 用户首次受该流式快照不一致影响;折叠规则应仅在 speaker 为空时清空名称。
SystemMiscUiHandler emits named NARR/narrator events with a nonempty speaker and isSystem=True, and the live React reducer renders them as named dialogs. However, fold_event_into_snapshot() clears characterName for every system event. Reconnect, polling recovery, and later-opened Chat Stage clients therefore hydrate the same narrator without its name. Preserve nonempty speakers while folding snapshots.
置信度 95% · 可直接回复讨论 · 👍/👎 会影响后续审查
| return "native" if mode == "native" else "react" | ||
| def _chat_runtime_mode(_state: BridgeState) -> str: | ||
| """Return the only supported chat UI runtime.""" | ||
| return "react" |
There was a problem hiding this comment.
[LOW · correctness] 关闭 React 聊天窗时不要吞掉运行时关闭失败 / Do not swallow React runtime close failures
Tauri 关闭路径会吞掉 closeRuntime() 的拒绝后继续执行 desktop_chat_window_destroy();该命令仅调用 window.destroy(),并未像 desktop_window_close 一样显式请求 bridge 关闭会话。因此当 /api/chat/close 在请求送达前失败时,聊天窗会消失,但 main.py 子进程和流式会话仍可能运行并阻止下一次启动。本行使 legacy native 配置首次进入这条 React 桌面关闭链路;应在销毁前保留窗口并报告失败,或让 Rust destroy 命令可靠地请求 bridge 关闭。
Original in English
The Tauri close path swallows a closeRuntime() rejection and still calls desktop_chat_window_destroy(). That command only invokes window.destroy() and does not explicitly request bridge shutdown like desktop_window_close. If /api/chat/close fails before delivery, the window can disappear while the main.py process and stream session remain running and block the next launch. Preserve the window on failure or make the Rust destroy command reliably request bridge shutdown.
置信度 88% · 可直接回复讨论 · 👍/👎 会影响后续审查
What changed
chat_ui_runtime_modevalues toreactin the backend config modelWhy
Native UI is no longer a supported user-selectable runtime. Existing installations may still persist
chat_ui_runtime_mode: native, so hiding the selector alone would leave those users on the old launch path.Impact
Existing native-mode configurations are migrated in memory and saved back as React the next time Program Settings are saved. Chat launches now always create and attach the React stream session. The legacy native implementation remains in the codebase for internal compatibility, but it is no longer reachable through configuration.
Checks
python -m pytest test/unit/frontend_bridge_core/test_chat_init.py test/unit/frontend_bridge_core/test_frontend_chat_launch_paths.py test/unit/frontend_bridge_core/test_chat_runtime_mode.py test/unit/config/test_schema.py -q(48 passed)pnpm test -- src/test/features/system-settings/SystemSettingsPage.test.tsx(8 passed)pnpm lint:typesSummary by Ameath
小爱先把这次核对的重点轻轻理顺。
此 PR 将遗留 native 聊天配置统一迁移到 React Chat Stage,并从程序设置中移除了运行时选择器和原生主题 JSON 入口。
Enhancements
SystemConfig、桥接启动路径和设置保存均固定使用 React 流式运行时。Tests
Original summary in English
This PR migrates legacy native chat configurations to React Chat Stage and removes the runtime selector and native theme JSON entry from Program Settings.
Enhancements
SystemConfig, the bridge launch path, and settings persistence now consistently use the React streaming runtime.Tests