全新7天新手引导初版以及新手引导结束后的破冰剧场#1658
Conversation
- 抽出教程高亮控制与打断分支模块,统一圆形/矩形高亮、轻微打断和生气退出的生命周期清理。 - 修复插件管理页新手引导的跳过、打断和生气退出流程:触发时立即清理高亮与 ghost cursor,语音结束后走统一 skip,不再误走 done。 - 完善插件管理页旁白恢复、桌面 bridge 事件、资源版本与回归测试。 - 更新 4 日新手教程设计文档,明确通用模块与页面专属适配层边界。 验证:npm run type-check;pytest tests/test_agent_rewrite_regression.py -q;pytest tests/frontend/test_home_prompt_flow.py -q
Why: pan clamp 到 ±1 时 StereoPannerNode 等于完全单声道,体验割裂。 加 SPATIAL_AUDIO_MAX_PAN=0.85 常量,clamp 收紧到 ±0.85,另一边 保留 ~12%(mono 源)/15%(stereo 源)信号。 Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…1376) * fix(linux): repair steamworks source launch support - 为 httpx 启用 socks extra,修复 SOCKS 代理环境下后端启动失败。 - 调整 Linux Steamworks 加载方式,兼容 SteamAPI_InitSafe 并保留 shutdown fallback。 - 对 Linux 缺失或不安全的 Steamworks wrapper 方法做降级,避免初始化和工坊详情查询崩溃。 - 源码启动时根据 Steamworks 可用状态正确暴露 Steam Cloud 会话状态。 * Fix unsupported workshop UGC details handling - Add a dedicated unsupported-details path instead of returning empty query results - Let workshop lists and cache warmup degrade while item detail returns explicit partial metadata - Cover unsupported wrapper behavior with unit tests * Fix workshop download tuple progress - Read GetItemDownloadInfo tuple values as downloaded, total, progress - Cover unsupported UGC detail partial responses with tuple progress data * fix(linux): smooth Live2D X11 rendering - 为 Linux X11 桌面运行时设置更保守的默认 Live2D 渲染质量\n- 在交互和拖动期间短暂提升 Live2D ticker 帧率,空闲后恢复用户配置\n- 优化 X11 下 Cubism renderer profile 和 UI 按钮轮询,降低透明窗口卡顿\n- 缓存 Live2D 参数索引并在模型就绪后通知桌面输入区域刷新 * fix(linux): preserve Live2D runtime state - 保留 X11 交互提帧中的 VSync/不限帧语义,并限制恢复 timer 只写回原 ticker - 保留 live2d-model-ready 事件中的真实模型路径或 url,避免监听方收到空路径 - 支持 saved 参数中的 param_<index> 与数字索引键,并按当前常驻表情动态排除保存参数 * fix(linux): align storage and locale behavior 修正 Steam Auto-Cloud 启动会话判定,避免非源启动时误判 Steam 会话可直接自动导入。 让 Lifekit 插件读取全局语言作为宿主 locale,并补充对应单元测试。 完善存储位置完成页的推荐路径、打开目录按钮与 8 个 locale 文案,更新前端测试覆盖 openPath 行为。 * fix(storage): harden host path actions 让存储完成页通过 { path } 对象调用 host.openPath,与桌面桥接既有合约保持一致。 推荐路径为空时禁用推荐按钮并阻止提交空路径,补充前端覆盖。 等待 openPath 测试回调完成后再断言,避免异步 race。 * fix: stabilize Linux subtitle bottom dragging Keep the Linux subtitle display anchored to the bottom of its transparent window and skip renderer-side moveTo bouncebacks that fight with native dragging near the lower work-area edge. * fix(agent): clarify Linux desktop automation readiness 修复 Linux 桌面端 pyautogui 显示会话不可访问时被误报为未安装的问题。\n\n同时补充后端截图缺少 gnome-screenshot 时的错误归一化,避免已安装 Pillow 时继续提示 Pillow 缺失。\n\n验证:\n- uv run python -m pytest tests/unit/test_agent_runtime_intent.py tests/unit/test_system_screenshot_router.py -q
* feat(galgame): add cross-platform capture dispatch and macOS/Linux window scanning Introduce platform detection module (`capture_platform.py`) to centralize OS checks and dispatch window scanning to platform-specific backends. Add `electron_capture.py` for Linux Wayland via Electron's desktopCapturer HTTP bridge, and `window_scanner_macos.py` for macOS using Quartz/CoreGraphics. Include unit tests for platform detection and dispatch logic. This enables the galgame plugin to capture game windows on macOS and Linux (Wayland/X11) in addition to existing Windows support, resolving cross-platform capture gaps. * fix: validate loopback host and normalize base URL in electron capture backend * fix(galgame): close linux xlib display after rect lookup * fix(galgame): crop electron capture frames by profile * fix: handle Quartz returning None in macOS window capture functions Add defensive checks for when Quartz.CGWindowListCopyWindowInfo returns None, which can occur when the window server is unavailable or during system transitions. This prevents AttributeError crashes in both window scanning and OCR target rectangle detection. * fix: remove error body detail from electron capture and raise on unresolvable Linux window rect - Remove `_MAX_ERROR_BODY_CHARS` and error body detail from `ElectronCaptureBackend` to avoid leaking sensitive information in error messages. - Change `_target_window_rect_linux` to raise `RuntimeError` instead of returning a fallback `(0, 0, width, height)` rectangle, preventing silent mis-capture for non-origin windows on Linux. - Add test for the new failure behavior and update existing tests accordingly. * fix: improve macOS window rect resolution and cross-platform foreground detection - Remove unused `_available_cached` field from ElectronCaptureBackend - Replace silent fallback rect with explicit RuntimeError in macOS window rect resolution to prevent incorrect screen coordinate capture - Add platform-aware foreground window detection that only uses Windows API when running on Windows platform - Ensure `is_foreground` field is properly preserved from original window data on non-Windows platforms * feat: improve Wayland detection and capture backend selection Add `is_linux_wayland_session()` function to properly detect Wayland sessions by checking both `XDG_SESSION_TYPE` and `WAYLAND_DISPLAY` environment variables. Update capture backend logic to prefer Electron's portal-backed path on Wayland for automatic selections, as MSS/PyAutoGUI can return black frames. Refactor smart backend selection to handle background capture scenarios more robustly on non-Windows platforms. * fix: improve Linux Wayland detection and process name fallback logic - Refactor `is_linux_wayland_session()` to correctly handle XWayland sessions by checking both session type and display variables - Fix `_linux_process_name_from_pid()` to prioritize reading the executable path over the comm file for more accurate process names - Update `Win32CaptureBackend` to handle non-Windows, non-Linux hosts properly in backend selection - Add test coverage for XWayland display scenario * fix: allow pixel-based capture fallback on Linux X11 for background windows On Linux X11, pixel-based capture backends can capture background windows, so the smart capture backend should allow falling back to them when window-level backends are unavailable. This change adds a check for Wayland sessions to only restrict pixel backends on Wayland, where they cannot capture background windows. * fix: skip Win32 foreground probe on non-Windows platforms The foreground window API is Windows-specific and should not be called on non-Windows platforms. Added a platform check to conditionally use the Windows foreground API, preventing crashes on Linux/macOS. Updated the force window scan logic to also respect this platform check. Added a unit test to verify the fix.
…oject-N-E-K-O#1421) * feat(proactive): mini-game 邀请 cooldown 按 accept/decline 拆 (2h/5h) 原本 accept 和 decline 走同一个 1h+10 chats 冷却,体感"刚拒绝又被问"。 现在按 state.last_response_choice 拆阈值:accept=2h(刚玩完一局短一些)、 decline=5h(明确不想玩,延长静默);chats 门 10 仍共用。 - config: MINI_GAME_INVITE_COOLDOWN_SECONDS → 拆成 _AFTER_ACCEPT_SECONDS (2h) + _AFTER_DECLINE_SECONDS (5h) - state: 新增 last_response_choice 字段;accept/decline 写入,later / record_delivered 清回 None - _mini_game_invite_in_cooldown: 按 choice 取阈值,遗留 state(字段缺失) fallback 走短的 accept 阈值,避免无端拉长 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(proactive): pin legacy fallback → accept 阈值(CodeRabbit nitpick N1) last_response_choice=None 时走 accept (2h) 而非 decline (5h) 是隐式契约, pin 住一条专用测试避免未来 fallback 误改成 decline 把已升级用户额外卡 3h。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(galgame): add host play character strategy mode * fix(galgame): address host play review findings * fix(galgame): localize character anchor prompt * fix(galgame): persist cross-scene memory updates * fix: clear character mode and fixed name on game bind When binding a new game, the character mode and fixed name state were not being reset, which could cause stale configuration to persist. This change ensures that `character_mode`, `character_fixed_name`, and `character_mode_stale` are properly cleared in both runtime state and persisted storage, preventing unintended behavior from previous character profile configurations. * fix(galgame): refresh zero-seq story summaries * fix(galgame): load user-only character profiles * fix(galgame): preserve user character profiles without preset * fix(galgame): preserve restored character profile state
…过 fallback,并修正相关注释,防止已有自主呼吸模型出现双重叠加
_updateRuntimeBreath(...) 接收 excludedParamIds,只跳过这些参数 motion 接管也改成按参数记录,避免 motion 只驱动 ParamBreath 时让 ParamBreath2/3 静态
…会被视为原生驱动,避免 fallback 双重写入
…dex。同一个 motion 生命周期内,只要某个 ParamBreath* 被检测到由 motion 写过,就会持续从 fallback 里排除;motion 结束或切换后才清空。这样 plateau/flat 帧不会被 _updateRuntimeBreath 抢写
- 调整主页新手教程聊天窗高亮生命周期,在首句欢迎语播放完后清除高亮 - 避免后续教程叙述流程重新高亮聊天窗 - 修复插件管理页预览阶段跳过按钮点击转发逻辑 - 支持鼠标与触摸事件按主页跳过按钮范围转发点击,避免点击穿透
- 收紧插件管理页跳过请求转发条件 - 坐标缺失、非法或不在主页跳过按钮范围内时拒绝转发 - 保持按钮不可用时走原有兜底处理
- 仅允许 pointerdown、mousedown、touchstart 和 click 触发主页跳过按钮转发 - 避免 touchmove 拖动经过跳过按钮区域时误取消新手教程
# Conflicts: # static/yui-guide-director.js
copy: refine zh-CN icebreaker scripts
|
Complex PR? Review this PR in Change Stack to move by importance, not file order. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Walkthrough新增并统一七日教程设计规范与迁移计划;实现破冰(icebreaker)后端接口与会话回填;扩展 React 紧凑输入与工具请求;重构插件运行时与跨页桥接;新增 Avatar Floating Guide 播放/重置实现及多套 Playwright/静态/VM 诊断脚本喵。 Changes七日教程与破冰系统主线
Sequence Diagram(s)(本次变更范围广泛,已在隐藏 artifact 中提供一幅序列图,展示 icebreaker 写入/回填与 overlay/chat relay 的高层交互喵。) Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes喵。 Possibly related PRs
Poem
|
- 补齐 static/locales 全语言同一行区间的文案更新 - 恢复 zh-CN importImagePartial,避免删除仍被代码引用的翻译 key - 消除 plugin-manager zh-CN 的单语言差异
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
For the zh locale this mapping makes resolveGuideAudioSrc request /static/assets/tutorial/guide-audio/zh/有了它们,我不光能看.mp3, but this commit deletes that zh MP3 (and the zh files referenced by interrupt_resist_light_3 / interrupt_angry_exit below). In Chinese sessions the plugin-dashboard tutorial and interrupt/angry-exit narration therefore 404 and falls back to a silent wait instead of playing the recorded guide audio.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| history = getattr(getattr(self, "session", None), "_conversation_history", None) | ||
| if not isinstance(history, list): | ||
| return False |
There was a problem hiding this comment.
Preserve icebreaker context for audio sessions
When the user remains in the default audio mode, self.session is an OmniRealtimeClient and does not expose _conversation_history, so every /api/game/new_user_icebreaker/context call is only queued and _flush_pending_icebreaker_context has the same guard. The endpoint still reports success, but the just-finished icebreaker Q/A never reaches the active realtime conversation, so the next voice turn lacks that handoff context unless the user later starts a text session.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
static/app-buttons.js (1)
1972-1979:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
_textSessionStartPromise缺少取消收敛,会把后续文本发送永久卡死喵这里把文本启动串到了同一个
mod._textSessionStartPromise上是对的,但这个内部sessionStartPromise只会靠S.sessionStartedResolver/Rejecter或 15 秒超时来 settle 喵。现在重置分支在 Line 1972-1979 会直接清空这两个全局回调;一旦发生在启动中,Line 2359-2374 的 timeout 也不会再 reject,结果就是_textSessionStartPromise永远 pending,后面所有sendTextPayloadInternal()都会卡在 Line 2392,文本聊天只能刷新页面恢复喵。推荐给这条 cached promise 加一个本地的 reject/cancel hook,并在 reset / cancelPendingSessionStart 这类会清空
S.sessionStartedResolver/Rejecter的路径里显式把它 reject 掉喵。🐾 一个可行的修复方向喵
+ function rejectPendingTextSessionStart(error) { + if (typeof mod._rejectTextSessionStart === 'function') { + mod._rejectTextSessionStart(error || new Error('TEXT_SESSION_START_CANCELLED')); + } + } if (!mod._textSessionStartPromise) { mod._textSessionStartPromise = (async function () { + var rejectStart = null; var sessionStartPromise = new Promise(function (resolve, reject) { + rejectStart = reject; S.sessionStartedResolver = resolve; S.sessionStartedRejecter = reject; - if (window.sessionTimeoutId) { clearTimeout(window.sessionTimeoutId); window.sessionTimeoutId = null; } }); + + mod._rejectTextSessionStart = function (error) { + if (!rejectStart) return; + var rejecter = rejectStart; + rejectStart = null; + rejecter(error || new Error('TEXT_SESSION_START_CANCELLED')); + }; ... })().finally(function () { + mod._rejectTextSessionStart = null; mod._textSessionStartPromise = null; }); }然后在重置/取消路径清空
S.sessionStartedResolver/S.sessionStartedRejecter之前,先调用rejectPendingTextSessionStart(...)喵。Also applies to: 2335-2392
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/app-buttons.js` around lines 1972 - 1979, The cached promise mod._textSessionStartPromise can become permanently pending because S.sessionStartedResolver/S.sessionStartedRejecter are nulled in cancel/reset paths without rejecting the local promise; add a local reject/cancel hook (e.g., implement a rejectPendingTextSessionStart that holds a local reject for the created sessionStartPromise) and call it before clearing S.sessionStartedResolver/S.sessionStartedRejecter in cancelPendingSessionStart/reset branches so the internal promise is explicitly rejected and sendTextPayloadInternal won't hang.static/app-interpage.js (1)
478-485:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift切模失败后没有把旧模型界面恢复回来喵
这里在新模型真正 ready 之前,就先把旧容器隐藏、旧 renderer 暂停、旧 overlay 清掉了;但
catch里只回滚了lanlan_config,没有按oldModelType/oldLive3dSubType把旧容器重新显示并恢复渲染。跨类型切换如果在init*Model、loadModel或 MMD 待机动画阶段失败,页面会停在空白或半隐藏状态,用户只能手动再切一次或者刷新喵。根因还是“先破坏旧状态,再尝试新状态”,建议要么把 hide/cleanup 延后到新模型加载成功后再提交,要么在异常分支做完整 restore/resume(连同 MMD loading session 一起收尾)喵。Also applies to: 488-667, 930-944
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/app-interpage.js` around lines 478 - 485, The change hides and tears down the previous model (calls to cleanupLive2DOverlayUI, cleanupVRMOverlayUI, cleanupMMDOverlayUI and pausing renderers) before the new model is confirmed ready, but the catch path only reverts lanlan_config and does not restore the old UI/render state; update the flow so either (A) delay hiding/cleanup until after init*Model/loadModel completes successfully, or (B) in every catch/failure branch perform a full restore using oldModelType and oldLive3dSubType to re-show the old model container(s), resume the old renderer(s) and reinitialize any MMD loading session teardown/resume logic; ensure you reference and restore behavior handled by cleanupLive2DOverlayUI, cleanupVRMOverlayUI, cleanupMMDOverlayUI and any renderer pause/resume helpers so the page never remains blank after a failed switch.
🧹 Nitpick comments (5)
docs/design/avatar-floating-day4-companion-guide-dev.md (1)
71-71: ⚡ Quick winscene 表缺失 privacy_mode 的面板关闭操作喵~
第 71 行 scene 表中
day4_privacy_mode的 operation 列是"无",但第 132 行的详细描述明确要求"本句台词播放完毕后调用closeSettingsPanel()收起设置弹窗"喵。这会让实现者看着 scene 表以为该 scene 不需要任何 operation,结果遗漏关闭设置面板的步骤喵。建议在 scene 表第 71 行的 operation 列补充:
close-settings-panel,让 scene 表与详细描述保持一致喵。这样后续day4_model_lockscene 进入时的cleanupBefore(第 137 行)就能明确知道设置面板应该已经被 privacy_mode 关掉了喵~🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/design/avatar-floating-day4-companion-guide-dev.md` at line 71, The scene table entry for day4_privacy_mode is missing the panel-close operation: update the operation column for `day4_privacy_mode` from "无" to `close-settings-panel` so it matches the detailed description that calls `closeSettingsPanel()` after the line plays; this ensures subsequent scenes like `day4_model_lock` and its `cleanupBefore` logic can rely on the settings panel already being closed.docs/design/avatar-floating-day2-screen-voice-guide-dev.md (1)
26-26: ⚡ Quick win确认 cursor 锚点在 scene 切换时的保留机制喵~
第 26 行要求"台词结束后清理聊天窗高光,保留上一段可见 cursor 锚点,供下一句平滑移动使用",这个要求对于 Ghost Cursor 在场景间平滑移动至关重要喵。但是后续的 scene 描述(如第 27-28 行)并没有明确说明如何使用这个锚点来启动平滑移动喵。
建议在"代码入口"章节(第 34-60 行)补充 cursor 锚点保留与复用的实现指引,比如:
- 哪个 API 负责保存/读取锚点坐标
- 锚点失效时的降级策略(是直接跳转还是使用默认起点)
- 外置聊天窗模式下锚点如何跨窗口同步
这样实现者就不会遗漏这个细节,避免 cursor 在切 scene 时出现跳跃或闪烁啦喵~
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/design/avatar-floating-day2-screen-voice-guide-dev.md` at line 26, Add explicit guidance in the "代码入口" section describing the cursor-anchor retention and reuse: define APIs saveCursorAnchor(anchor), readCursorAnchor(sceneId), invalidateCursorAnchor(sceneId) and syncAnchorAcrossWindows(windowId, anchor) and explain that playback end should call saveCursorAnchor(currentVisibleAnchor) while scene-entry should call readCursorAnchor(sceneId) and, if present and valid, call smoothMoveFromAnchor(anchor) to start Ghost Cursor's smooth move; if readCursorAnchor returns missing/expired, fall back to a configurable defaultStartPoint or perform an immediate jump; document anchor expiry rules and when to call invalidateCursorAnchor, and add notes on how external chat-window mode must propagate anchors via syncAnchorAcrossWindows to keep anchors synchronized across windows.docs/design/avatar-floating-day5-personalization-guide-dev.md (1)
76-76: ⚡ Quick win明确"模型管理、声音克隆与 API Key"入口的高亮策略喵~
第 76 行 scene 表中
day5_character_settings的 target 列只写了"角色设置按钮、settings-sidepanel:character-settings",但第 83 行详细描述提到"模型管理、声音克隆与 API Key 等入口只认门"喵。这里"认门"的视觉呈现不够明确喵:
- 是只高亮整个 character-settings 侧边栏,让用户自己看到里面有这些入口?
- 还是要像 Day 4 那样用 Ghost Cursor 依次 tour 这些入口按钮?
- 还是给这些入口加单独的 secondary spotlight?
建议在第 76 行的"说明"列或第 83 行的动作描述中补充明确的视觉处理方式,比如"Ghost Cursor 在侧边栏内做椭圆运动时会短暂经过模型/声音/API 区域,但不停留指认"或"只高亮侧边栏容器,不单独高亮内部入口"喵。这样实现者就知道该用什么样的 spotlight 策略啦喵~
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/design/avatar-floating-day5-personalization-guide-dev.md` at line 76, Clarify the visual highlight strategy for day5_character_settings (target: settings-sidepanel:character-settings) by updating the scene table row or the action description to explicitly state which of the three behaviors to use (only highlight the entire character-settings sidepanel container, use Ghost Cursor to tour internal model/voice/API buttons with elliptical passes without pausing, or apply individual secondary spotlights to those internal entries); pick one strategy and add a single-sentence instruction such as "Ghost Cursor will move elliptically inside settings-sidepanel:character-settings and briefly pass over the Model Management / Voice Cloning / API Key areas without pausing" or "Only highlight the settings-sidepanel:character-settings container; do not spotlight internal entries" so implementers know exactly which spotlight/ghost-cursor behavior to implement.frontend/react-neko-chat/src/App.test.tsx (1)
2831-2831: ⚡ Quick win避免重复测试名称,便于精准定位失败用例喵
Line 2831 的用例名与前文 Line 2754 完全同名,Vitest 虽可执行,但
-t过滤和失败定位会出现歧义,建议改成唯一名称喵。✂️ 建议修改
-it('shows tutorial guide streaming text in the compact capsule immediately', () => { +it('shows day1 tutorial guide streaming text in the compact capsule immediately', () => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/react-neko-chat/src/App.test.tsx` at line 2831, The test case with description "shows tutorial guide streaming text in the compact capsule immediately" is duplicated (previously declared earlier), so rename the second it(...) description to a unique, descriptive string (e.g., append " - compact capsule duplicate" or a clearer variant) to avoid ambiguity with Vitest's -t filtering and failure reporting; locate the duplicate by finding the it(...) call that uses that exact description and update its string literal (and any test references in CI/test filters) so each test name is unique.static/app-proactive.js (1)
122-147: ⚡ Quick win主人主人!这里缺少文档注释了喵~
isNewUserIcebreakerPeriodActive()是新增的公共 API(line 147 导出到window.appProactive),但是没有文档注释喵。对比项目中其他公共函数(比如canTriggerProactively()line 415),都有清晰的注释说明功能和逻辑喵。建议补充 JSDoc 注释,说明:
- 功能:检查新用户破冰期(Day 1-7)是否处于活跃状态
- 返回值:
true= 破冰进行中,主动搭话应被抑制;false= 未开始或已完成- 实现逻辑:优先使用
window.newUserIcebreaker.getActiveSession()API,回退到 localStorage;Day 7 completed 视为破冰期结束这样其他开发者调用这个 API 时就能快速理解啦喵~
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/app-proactive.js` around lines 122 - 147, Add a JSDoc block for the exported function isNewUserIcebreakerPeriodActive (exported as mod.isNewUserIcebreakerPeriodActive) describing its purpose, return value, and algorithm: state that it checks whether the new-user icebreaker period (Days 1–7) is active, that it returns true to indicate the icebreaker is in progress (so proactive triggers should be suppressed) and false when not started or completed, and that it prefers window.newUserIcebreaker.getActiveSession() then falls back to readNewUserIcebreakerStore() / localStorage logic and treats Day 7.completed === true as the period ended; mirror the style and tags used by nearby public APIs like canTriggerProactively() (e.g., `@returns`, brief description) so other developers can quickly understand usage and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/design/avatar-floating-day3-agent-guide-dev.md`:
- Around line 14-15: Update the scene table entry for "day3_avatar_tools_props"
to include the precondition that the arc tool menu must remain open; explicitly
state: "前置条件:弧形工具菜单必须保持 open 状态,否则三个道具不会渲染". Mention the React render condition
variables toolMenuOpen and compactInputToolFanOpen and that calling
setAvatarToolMenuOpen(true) alone is insufficient if the arc menu is closed, so
implementers handling the transition from day3_avatar_tools to
day3_avatar_tools_props will keep the arc menu open.
In `@docs/design/avatar-floating-day6-agent-guide-dev.md`:
- Line 95: Update the acceptance criteria for the
day6-plugin-dashboard-handoff-flow to cover handoff failures: in the flow
definitions (day6_plugin_side_panel, day6_plugin_dashboard) and the named flow
day6-plugin-dashboard-handoff-flow, add a requirement that the handoff includes
timeout and failure handling—if the plugin page fails to open, its runtime fails
to load, it does not accept handoff, or no completion signal is received within
a configurable timeout, the homepage must automatically restore the Ghost Cursor
to its original position and clean up temporary state (Agent panel, user plugin
side panel, management panel highlights); ensure this is appended to the
checklist referenced around lines 115–122 so automated/manual tests validate
timeout and recovery paths.
In `@docs/design/home-yui-guide-lifecycle-modularization.md`:
- Line 225: The document references the wrong module name: replace the incorrect
literal "static/tutorial-interrupt-controller.js" with the correct module name
"static/tutorial-interaction-takeover.js" where the text describes the "交互接管"
fallback behavior, and update any adjacent wording so the module name and the
described “交互接管” downgrade behavior are consistent.
In `@frontend/plugin-manager/src/yui-guide-runtime.ts`:
- Around line 854-855: The injected CSS selector has a trailing comma making it
invalid—remove the comma from "body.yui-taking-over.yui-user-cursor-revealed
#${ROOT_ID} .yui-guide-plugin-spotlight" so the rule "{ cursor: auto !important;
}" applies; also restore real DOM-side cursor visual effects in clickCursor()
and resetCursorVisualState() (they are currently empty) by toggling the same
attributes/classes the rest of the code expects: set/remove
data-yui-cursor-hidden on the root/spotlight element and add/remove the
yui-user-cursor-revealed / yui-taking-over related classes (or the exact class
names used elsewhere) so clickCursor() applies the "hidden" state and any
click-feedback class and resetCursorVisualState() undoes them, ensuring the
data-yui-cursor-hidden ↔ yui-*-cursor-revealed linkage is consistent with the
rest of the rendering code.
In `@frontend/react-neko-chat/src/App.tsx`:
- Around line 396-401: The UI has two bugs: menu/fan state drift and an
off-by-two truncation; fix by (1) ensuring any code that sets toolMenuOpen
(calls to setToolMenuOpen(true) / toolMenuOpen = true) only does so when the
same guard used to open the fan is satisfied (mirror the payload check used when
opening the fan—if payload exists, do not set toolMenuOpen true and explicitly
set it false when closing the fan) so the menu cannot be opened while the fan is
blocked, and (2) update truncateCompactPreview to enforce the strict maxLength
by reserving 3 chars for "..." (use text.slice(0, Math.max(0, maxLength - 3)))
so the returned string including "..." never exceeds maxLength; reference
truncateCompactPreview and any setToolMenuOpen / toolMenuOpen usages and the
fan-open guard to locate changes.
In `@main_routers/game_router.py`:
- Around line 4767-4774: The handler currently does await request.json() and
then calls data.get(...), which raises AttributeError for non-object JSON; fix
by normalizing the parsed body into a dict before accessing keys—either call the
existing helper _read_json_object(request) or, after parsing, do if not
isinstance(data, dict): data = {}—then continue using data.get("role") /
data.get("text") so the invalid_role / missing_text branches handle bad payloads
instead of raising a 500.
In `@scripts/diagnose_pc_day1_capsule_wobble.py`:
- Around line 48-72: The script's run_static_checks (and related extraction code
using _read and direct split(...)[1] into greeting_block, capsule_block,
legacy_externalized_intro_block, greeting_play_block) must not raise on missing
fragments or environment issues; implement a safe extractor helper (e.g.,
safe_extract(source, start_token, end_token) that returns None or raises a
controlled exception) and replace raw split(...)[1] calls with it, add preflight
checks in main for pc_repo.exists() and for node availability (catching OSError
or checking shutil.which("node")), and where subprocess.run(["node", ...]) is
used wrap it in try/except and treat failures by appending a failing Check
result (with error details) instead of letting the exception bubble; ensure all
exceptions during extraction or subprocess execution are caught and converted
into Check objects returned by run_static_checks rather than tracebacks.
In `@scripts/monitor_pc_day3_avatar_tools.py`:
- Around line 256-278: The snapshot() currently only records toolIds/toolCount
(DOM presence) which lets analyze() misinterpret final chatState values as the
state at click time; update snapshot() to also record visibleToolIds and
visibleToolCount (only include nodes that are actually visible) and record
fanInteractive per-frame, then change analyze() to use the frame closest to the
avatar click events (e.g., 'avatar-button-dom-click' or
'home-clickCursorAndWait-start') to read fanInteractive/aria-expanded rather
than using the final chatState values; reference snapshot(), analyze(),
toolIds/toolCount, visibleToolIds/visibleToolCount, and the
avatar-button-dom-click/home-clickCursorAndWait-start event frames when making
these changes.
In `@static/app-interpage.js`:
- Around line 2229-2243: The message handler added with
window.addEventListener('message', ...) currently trusts any message with
__nekoTutorialOverlayRelay and processes payloads via
shouldBypassYuiGuideMessageDedup/isDuplicateMessage and
handleYuiGuideRelayedMessage; add an explicit same-origin check (require
event.origin === window.location.origin) at the top of that handler and return
early if it fails so only same-origin postMessage events are processed.
In `@static/app-react-chat-window.js`:
- Around line 2460-2462: The submit-lock check currently only reads
state.homeTutorialInteractionLocked and state.homeTutorialInputLocked but init()
never syncs the actual runtime “in-tutorial” status, so a page refresh can leave
those flags false and allow submits; update init() to explicitly set
state.homeTutorialInteractionLocked and state.homeTutorialInputLocked from the
runtime/tutorial service (the same source that emits neko:tutorial-started) so
locks are seeded on load, and add a defensive fallback in handleComposerSubmit()
to also call isHomeTutorialInteractionLocked() (and prevent submit if it returns
true) to cover race/ missed-event cases.
In `@static/app-tutorial-prompt.js`:
- Around line 515-539: enforceHomeTutorialFeatureSuppression currently only
toggles Galgame and proactive keys when suppression.active is true and omits
reapplying the backend agent disable (the same behavior
beginHomeTutorialFeatureSuppression does), so extract a small helper (e.g.,
enforceAgentDisableOnly or applyAgentSuppressionWithoutSnapshotRestore) that
performs the backend calls to /api/agent/command and /api/agent/flags to
force-disable the agent without touching suppression.snapshot, then invoke that
helper from both branches of enforceHomeTutorialFeatureSuppression (the branch
that calls beginHomeTutorialFeatureSuppression and the suppression.active ===
true branch after setGalgameState/applyProactiveState) so
window.NekoHomeTutorialFeatureController.enforce() always ensures the backend
agent flags are disabled.
In `@static/app-websocket.js`:
- Around line 621-629: The code rewrites detail.reason to 'features-restored'
before calling _sendGreetingCheckIfReady, which hides
tutorial-completed/tutorial-skipped info and causes
isNewUserIcebreakerBlockingGreeting (which reads S._greetingCheckReason) and
sendHomeTutorialState to miss the true handoff reason; fix by preserving and
propagating the original detail.reason into S._greetingCheckReason before any
retry/_sendGreetingCheckIfReady call (or only trigger the retry from the later
neko:home-tutorial-lock-changed path), ensuring functions like
isNewUserIcebreakerBlockingGreeting and sendHomeTutorialState see the original
'tutorial-completed'/'tutorial-skipped' reason.
In `@static/avatar-floating-guide-reset.js`:
- Around line 562-593: markGuideRoundOutcome currently persists only the merged
outcome and loses the richer end-state (rawReason like "angry_exit"/"escape"),
and other code paths that start/reset a round don't clear lastEndState causing
ended=true to coexist with an active round; fix by persisting the full endState
object (including rawReason and any original flags) into state.lastEndState in
markGuideRoundOutcome and when emitting window.avatarFloatingGuideEndState, and
update any round-start/reset logic (where
currentRound/pendingRound/manualResetRound are set, e.g. functions that call
normalizeRound/loadGuideState/saveGuideState) to clear state.lastEndState when
opening or restarting the same day so an active round never has lastEndState.
Ensure loadGuideState/saveGuideState keep the full lastEndState shape so callers
can distinguish complete/skip/angry_exit/escape.
- Around line 660-667: detectModelPrefix() currently only maps cfg 'vrm' to
'vrm' and falls back to 'live2d', which misses the repository normalization that
maps 'vrm' to 'live3d' and causes createRoundPlayer() to cache the wrong prefix;
update detectModelPrefix() to treat window.lanlan_config.model_type === 'live3d'
(and/or normalize 'vrm' -> 'live3d') as returning 'vrm' or otherwise return the
correct prefix used by subsequent selectors, so that prefixes computed in
detectModelPrefix() (used by createRoundPlayer() and any cachedPrefix logic)
match the repo's normalized model_type and won't route VRM users to live2d
selectors.
In `@static/css/index.css`:
- Around line 295-303: 当前对 data-home-avatar-floating-guide-role="persistent" 和
"action" 的高亮样式没有检查高亮开关,导致在未高亮时也会发光;修改相关 selector(匹配属性值 persistent 和 action
的规则)使其同时要求 [data-home-avatar-floating-guide-highlight="true"],例如把现有
[data-home-avatar-floating-guide-role="persistent"] 和
[data-home-avatar-floating-guide-role="action"] 的规则改为仅在
[data-home-avatar-floating-guide-highlight="true"][data-home-avatar-floating-guide-role="..."]
时生效,从而避免未高亮态的视觉残留;保留现有 outline-color 和 filter 设置不变,仅调整选择器限定条件。
---
Outside diff comments:
In `@static/app-buttons.js`:
- Around line 1972-1979: The cached promise mod._textSessionStartPromise can
become permanently pending because
S.sessionStartedResolver/S.sessionStartedRejecter are nulled in cancel/reset
paths without rejecting the local promise; add a local reject/cancel hook (e.g.,
implement a rejectPendingTextSessionStart that holds a local reject for the
created sessionStartPromise) and call it before clearing
S.sessionStartedResolver/S.sessionStartedRejecter in
cancelPendingSessionStart/reset branches so the internal promise is explicitly
rejected and sendTextPayloadInternal won't hang.
In `@static/app-interpage.js`:
- Around line 478-485: The change hides and tears down the previous model (calls
to cleanupLive2DOverlayUI, cleanupVRMOverlayUI, cleanupMMDOverlayUI and pausing
renderers) before the new model is confirmed ready, but the catch path only
reverts lanlan_config and does not restore the old UI/render state; update the
flow so either (A) delay hiding/cleanup until after init*Model/loadModel
completes successfully, or (B) in every catch/failure branch perform a full
restore using oldModelType and oldLive3dSubType to re-show the old model
container(s), resume the old renderer(s) and reinitialize any MMD loading
session teardown/resume logic; ensure you reference and restore behavior handled
by cleanupLive2DOverlayUI, cleanupVRMOverlayUI, cleanupMMDOverlayUI and any
renderer pause/resume helpers so the page never remains blank after a failed
switch.
---
Nitpick comments:
In `@docs/design/avatar-floating-day2-screen-voice-guide-dev.md`:
- Line 26: Add explicit guidance in the "代码入口" section describing the
cursor-anchor retention and reuse: define APIs saveCursorAnchor(anchor),
readCursorAnchor(sceneId), invalidateCursorAnchor(sceneId) and
syncAnchorAcrossWindows(windowId, anchor) and explain that playback end should
call saveCursorAnchor(currentVisibleAnchor) while scene-entry should call
readCursorAnchor(sceneId) and, if present and valid, call
smoothMoveFromAnchor(anchor) to start Ghost Cursor's smooth move; if
readCursorAnchor returns missing/expired, fall back to a configurable
defaultStartPoint or perform an immediate jump; document anchor expiry rules and
when to call invalidateCursorAnchor, and add notes on how external chat-window
mode must propagate anchors via syncAnchorAcrossWindows to keep anchors
synchronized across windows.
In `@docs/design/avatar-floating-day4-companion-guide-dev.md`:
- Line 71: The scene table entry for day4_privacy_mode is missing the
panel-close operation: update the operation column for `day4_privacy_mode` from
"无" to `close-settings-panel` so it matches the detailed description that calls
`closeSettingsPanel()` after the line plays; this ensures subsequent scenes like
`day4_model_lock` and its `cleanupBefore` logic can rely on the settings panel
already being closed.
In `@docs/design/avatar-floating-day5-personalization-guide-dev.md`:
- Line 76: Clarify the visual highlight strategy for day5_character_settings
(target: settings-sidepanel:character-settings) by updating the scene table row
or the action description to explicitly state which of the three behaviors to
use (only highlight the entire character-settings sidepanel container, use Ghost
Cursor to tour internal model/voice/API buttons with elliptical passes without
pausing, or apply individual secondary spotlights to those internal entries);
pick one strategy and add a single-sentence instruction such as "Ghost Cursor
will move elliptically inside settings-sidepanel:character-settings and briefly
pass over the Model Management / Voice Cloning / API Key areas without pausing"
or "Only highlight the settings-sidepanel:character-settings container; do not
spotlight internal entries" so implementers know exactly which
spotlight/ghost-cursor behavior to implement.
In `@frontend/react-neko-chat/src/App.test.tsx`:
- Line 2831: The test case with description "shows tutorial guide streaming text
in the compact capsule immediately" is duplicated (previously declared earlier),
so rename the second it(...) description to a unique, descriptive string (e.g.,
append " - compact capsule duplicate" or a clearer variant) to avoid ambiguity
with Vitest's -t filtering and failure reporting; locate the duplicate by
finding the it(...) call that uses that exact description and update its string
literal (and any test references in CI/test filters) so each test name is
unique.
In `@static/app-proactive.js`:
- Around line 122-147: Add a JSDoc block for the exported function
isNewUserIcebreakerPeriodActive (exported as
mod.isNewUserIcebreakerPeriodActive) describing its purpose, return value, and
algorithm: state that it checks whether the new-user icebreaker period (Days
1–7) is active, that it returns true to indicate the icebreaker is in progress
(so proactive triggers should be suppressed) and false when not started or
completed, and that it prefers window.newUserIcebreaker.getActiveSession() then
falls back to readNewUserIcebreakerStore() / localStorage logic and treats Day
7.completed === true as the period ended; mirror the style and tags used by
nearby public APIs like canTriggerProactively() (e.g., `@returns`, brief
description) so other developers can quickly understand usage and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c8cfccea-94ad-44e6-b079-2073cf2315b2
⛔ Files ignored due to path filters (8)
static/assets/tutorial/guide-audio/zh/人类!你真的很没礼貌.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/你看,这里可以穿我的.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/好啦好啦,不霸占你的.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/当然啦,如果你想让本.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/微风、阳光,还有刚刚.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/有了它们,我不光能看.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/等一下啦!还没结束呢.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/还没完呢!你快看快看.mp3is excluded by!**/*.mp3
📒 Files selected for processing (92)
docs/design/avatar-floating-7day-complete-guide-dev.mddocs/design/avatar-floating-day1-home-guide-dev.mddocs/design/avatar-floating-day2-screen-voice-guide-dev.mddocs/design/avatar-floating-day3-agent-guide-dev.mddocs/design/avatar-floating-day4-companion-guide-dev.mddocs/design/avatar-floating-day5-personalization-guide-dev.mddocs/design/avatar-floating-day6-agent-guide-dev.mddocs/design/avatar-floating-day7-graduation-guide-dev.mddocs/design/avatar-floating-guide-feature-tree.mddocs/design/avatar-floating-panel-functions.mddocs/design/avatar-floating-pc-global-overlay-migration-plan.mddocs/design/avatar-floating-post-theater-chat-branches.mddocs/design/avatar-performance-module-maintenance.mddocs/design/day1-icebreaker-dialogue-tree.htmldocs/design/home-tutorial-yui-guide-performance-owner-stage-breakdown.mddocs/design/home-yui-guide-day1-dialogue-flow.mddocs/design/home-yui-guide-lifecycle-modularization.mddocs/design/home-yui-guide-text-highlight-cursor-flow.mddocs/design/new-user-icebreaker-handoff.mddocs/design/software-function-inventory-and-guide-gap-check.mdfrontend/plugin-manager/src/components/plugin/MarketPanel.vuefrontend/plugin-manager/src/yui-guide-runtime.tsfrontend/react-neko-chat/src/App.test.tsxfrontend/react-neko-chat/src/App.tsxfrontend/react-neko-chat/src/message-schema.test.tsfrontend/react-neko-chat/src/message-schema.tsmain_logic/core.pymain_routers/game_router.pymain_routers/pages_router.pymain_routers/websocket_router.pyplugin/plugins/galgame_plugin/screen_classifier.pyscripts/capture_day3_avatar_tools_capsule_frames.pyscripts/diagnose_ghost_cursor_resistance.pyscripts/diagnose_pc_capsule_spotlight.pyscripts/diagnose_pc_day1_capsule_wobble.pyscripts/monitor_pc_day1_intro_greeting_cursor.pyscripts/monitor_pc_day3_avatar_tools.pystatic/app-buttons.jsstatic/app-interpage.jsstatic/app-proactive.jsstatic/app-react-chat-window.jsstatic/app-tutorial-prompt.jsstatic/app-websocket.jsstatic/avatar-floating-guide-reset.jsstatic/css/index.cssstatic/css/yui-guide.cssstatic/icebreaker/icebreaker_scripts.jsonstatic/icebreaker/locales/en.jsonstatic/icebreaker/locales/es.jsonstatic/icebreaker/locales/ja.jsonstatic/icebreaker/locales/ko.jsonstatic/icebreaker/locales/pt.jsonstatic/icebreaker/locales/ru.jsonstatic/icebreaker/locales/zh-CN.jsonstatic/icebreaker/locales/zh-TW.jsonstatic/icebreaker/new-user-icebreaker.jsstatic/locales/en.jsonstatic/locales/es.jsonstatic/locales/ja.jsonstatic/locales/ko.jsonstatic/locales/pt.jsonstatic/locales/ru.jsonstatic/locales/zh-CN.jsonstatic/locales/zh-TW.jsonstatic/tutorial-highlight-controller.jsstatic/tutorial-interaction-takeover.jsstatic/tutorial-interrupt-controller.jsstatic/universal-tutorial-manager.jsstatic/yui-guide-avatar-stage.jsstatic/yui-guide-day1-home-guide.jsstatic/yui-guide-day2-screen-voice-guide.jsstatic/yui-guide-day3-interaction-guide.jsstatic/yui-guide-day4-companion-guide.jsstatic/yui-guide-day5-personalization-guide.jsstatic/yui-guide-day6-agent-guide.jsstatic/yui-guide-day7-graduation-guide.jsstatic/yui-guide-director.jsstatic/yui-guide-overlay.jsstatic/yui-guide-steps.jstemplates/api_key_settings.htmltemplates/chat.htmltemplates/index.htmltemplates/memory_browser.htmltests/frontend/test_home_prompt_flow.pytests/test_agent_rewrite_regression.pytests/unit/test_app_websocket_static.pytests/unit/test_avatar_floating_day1_round_contracts.pytests/unit/test_game_router.pytests/unit/test_new_user_icebreaker_context.pytests/unit/test_new_user_icebreaker_static_contracts.pytests/unit/test_react_chat_window_static.pytests/unit/test_websocket_home_tutorial_guard.py
💤 Files with no reviewable changes (1)
- plugin/plugins/galgame_plugin/screen_classifier.py
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (2)
docs/design/avatar-floating-guide-feature-tree.md (1)
359-394: ⚡ Quick win确认"待补能力"的实现计划喵~
冲突修复机制中提到的"待补能力"(软锁、输入框提示、工具栏自动切换、棒棒糖计数解锁等)目前标记为需要新增状态机与handler,但文档没有给出明确的milestone或跟踪方式喵。
建议补充:
- 是否需要在后续独立PR中实现?
- 当前Day 1-7主线是否可以在没有这些能力的情况下完整落地?
这样可以让reviewer明确当前PR的scope边界喵~
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/design/avatar-floating-guide-feature-tree.md` around lines 359 - 394, The doc lacks an implementation/tracking plan for the "待补能力" (soft-lock, input hint, toolbar auto-switch, lollipop count unlock and new state machine/handlers); add a short implementation section listing (a) whether each capability will be delivered in follow-up independent PRs or included in this PR, (b) milestones/timeline or Jira/Ticket IDs for each item, and (c) confirmation whether Day 1-7 main flow can fully run without these features (and what graceful degradations are used—e.g., reuse existing composerDisabled/home tutorial interaction lock and fallback UX), referencing the terms "软锁", "composerDisabled", "home tutorial interaction lock", and "棒棒糖计数解锁" so reviewers can verify scope and follow-up work.frontend/react-neko-chat/src/message-schema.ts (1)
170-176: ⚡ Quick win工具轮旋转约束的magic number需要注释说明喵~
compactToolWheelRotateRequestSchema中的direction约束为±1,stepCount的上限为7,这些数值的语义不够明确喵:
direction: 1/-1是顺时针/逆时针吗?还是向前/向后?stepCount.max(7)是基于什么业务规则?是工具轮的总位置数减一,还是单次最大允许的旋转步数?同样,Line 180的
index.max(6)意味着工具轮有7个位置,但这个约束分散在schema中,如果UI改为8个工具,这里容易被遗漏喵~💡 建议添加注释或导出常量喵
+// Compact tool wheel has 7 positions (index 0-6); direction ±1 means clockwise/counter-clockwise const compactToolWheelRotateRequestSchema = z.object({ id: z.string().min(1), - direction: z.union([z.literal(1), z.literal(-1)]), - stepCount: z.number().int().positive().max(7), + direction: z.union([z.literal(1), z.literal(-1)]), // 1: clockwise, -1: counter-clockwise + stepCount: z.number().int().positive().max(7), // max = total positions (7) reason: z.string().optional(), forceFast: z.boolean().optional(), }).nullable(); const compactToolWheelIndexRequestSchema = z.object({ id: z.string().min(1), - index: z.number().int().min(0).max(6), + index: z.number().int().min(0).max(6), // 7 positions: 0-6 reason: z.string().optional(), }).nullable();或者更好的方式是导出常量喵:
const COMPACT_TOOL_WHEEL_POSITIONS = 7; const compactToolWheelRotateRequestSchema = z.object({ id: z.string().min(1), direction: z.union([z.literal(1), z.literal(-1)]), // 1: clockwise, -1: counter-clockwise stepCount: z.number().int().positive().max(COMPACT_TOOL_WHEEL_POSITIONS), reason: z.string().optional(), forceFast: z.boolean().optional(), }).nullable(); const compactToolWheelIndexRequestSchema = z.object({ id: z.string().min(1), index: z.number().int().min(0).max(COMPACT_TOOL_WHEEL_POSITIONS - 1), reason: z.string().optional(), }).nullable();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/react-neko-chat/src/message-schema.ts` around lines 170 - 176, The schemas use unexplained magic numbers: clarify and centralize them by exporting a named constant (e.g., COMPACT_TOOL_WHEEL_POSITIONS) and replacing hard-coded bounds in compactToolWheelRotateRequestSchema and compactToolWheelIndexRequestSchema with that constant (use COMPACT_TOOL_WHEEL_POSITIONS for stepCount.max and COMPACT_TOOL_WHEEL_POSITIONS - 1 for index.max), and add a short inline comment on the direction union in compactToolWheelRotateRequestSchema (e.g., 1: clockwise, -1: counter-clockwise) so the intent and the source of the numeric limits are explicit and easy to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/design/avatar-floating-7day-complete-guide-dev.md`:
- Line 32: The table row titled "PC 全局透明教程 overlay(迁移目标)" currently contains a
developer absolute path; replace that absolute user-specific path with a
project-relative reference (for example use
"N.E.K.O.-PC/src/avatar-tool-cursor-service.js" or simply
"src/avatar-tool-cursor-service.js") so the document does not expose local
machine paths and uses repo-relative links instead.
In `@frontend/plugin-manager/src/yui-guide-runtime.ts`:
- Around line 2538-2540: The focus-check conditional is using
hasDesktopTutorialSkipBridge() but this code path actually depends on the
interrupt bridge; change the condition to use
hasDesktopTutorialInterruptBridge() (or check requestTutorialInterrupt
existence) instead of hasDesktopTutorialSkipBridge() so that when only the
interrupt bridge is implemented we don't early-return on document.hasFocus() ===
false; update the if that references document.hasFocus and
hasDesktopTutorialSkipBridge to reference
hasDesktopTutorialInterruptBridge/requestTutorialInterrupt accordingly.
- Around line 854-855: The injected stylesheet in yui-guide-runtime contains an
invalid selector trailing comma ("body.yui-taking-over.yui-user-cursor-revealed
#${ROOT_ID} .yui-guide-plugin-spotlight, {") — remove the extra comma so the
selector is valid (reference ROOT_ID and .yui-guide-plugin-spotlight in the
injected CSS); and in handleInterrupt() replace the incorrect bridge check using
!this.hasDesktopTutorialSkipBridge() with the proper desktop interrupt
capability check (use this.hasDesktopTutorialInterruptBridge() or
this.canRequestHomeInterruptPlayback() which corresponds to
requestTutorialInterrupt) so the logic correctly detects when the desktop can
accept tutorial interrupt requests.
In `@frontend/react-neko-chat/src/App.test.tsx`:
- Around line 2831-2850: The test title "shows tutorial guide streaming text in
the compact capsule immediately" is duplicated; locate the `it('shows tutorial
guide streaming text in the compact capsule immediately', ...)` test in
App.test.tsx and rename one of the duplicates to a unique, more specific name
(e.g., include "compact mode" or "when streaming tutorial guide") so test
reports can be distinguished; update any related test references/comments
accordingly.
In `@frontend/react-neko-chat/src/App.tsx`:
- Around line 396-400: The truncateCompactPreview function currently slices to
maxLength - 1 then appends "..." causing the final string to exceed maxLength;
update truncateCompactPreview to ensure the returned string length is never
greater than maxLength by slicing to maxLength - 3 and appending "..." when
maxLength is >= 3, and for very small maxLength values (e.g., 0–2) return an
appropriate shortened result (such as a slice of text up to maxLength without
ellipsis) so the final output respects COMPACT_PREVIEW_MAX_LENGTH.
- Around line 3014-3016: openCompactInputToolFan currently returns early when
compactInputHasPayload or composerDisabled, but request effects are already
writing last*RequestIdRef before the actual open, causing host requests to be
swallowed; change the flow so the request id is only recorded when the tool fan
actually opens — i.e., move the assignment/updating of
lastCompactInputToolFanRequestIdRef (and analogous last ids for
compactToolFanOpenRequest and compactToolWheelRotateRequest) into the branch
after clearCompactInputToolFanCloseTimer() where the open succeeds (or into the
success path inside compactToolFanOpenRequest/compactToolWheelRotateRequest),
and ensure the early-return path does not mutate those last*RequestIdRef values
so requests aren’t permanently deduped when compactInputHasPayload or
composerDisabled prevents opening.
In `@main_routers/game_router.py`:
- Around line 4767-4773: The handler currently assumes request.json() returns a
dict and calls data.get(...), causing AttributeError for valid non-object JSON
(like [] or "x"); after parsing JSON (the await request.json() call) add a type
check that ensures data is a dict (or at least has .get) and if not immediately
return {"ok": False, "reason": "invalid_body"} before accessing data.get for
role and text (see the block that assigns role and text).
In `@main_routers/pages_router.py`:
- Line 79: The static_asset_version calculation currently only includes
_PROJECT_ROOT / "static/icebreaker/locales/zh-CN.json", so other locale files
(e.g., en.json, es.json) won't trigger a version bump; update the code that
builds static_asset_version (the list/collection that contains _PROJECT_ROOT /
"static/icebreaker/locales/zh-CN.json") to include all locale files under
static/icebreaker/locales (either by adding the explicit files en.json and
es.json or, better, by globbing the directory and extending the list with every
*.json in that folder) so any locale change updates the static asset version.
In `@scripts/diagnose_pc_capsule_spotlight.py`:
- Around line 211-212: The function run_pc_static_checks currently returns an
empty list which falsely signals "all checks passed"; update
run_pc_static_checks to return at least one explicit Check indicating a skipped
or failed result (e.g., a Check with status SKIP or FAIL and a message like "PC
static checks not implemented") so the overall diagnoser does not show a
misleading all-green result; construct the Check using the existing Check
type/constructor used elsewhere in the file (include the function name
run_pc_static_checks and the Check class/enum symbols to locate and mirror the
project's Check creation pattern).
In `@scripts/diagnose_pc_day1_capsule_wobble.py`:
- Around line 652-653: The code calls run_static_checks(args.pc_repo) directly
in main which will raise a traceback if the provided --pc-repo path is missing
or invalid; before invoking run_static_checks, validate args.pc_repo with
os.path.exists() and os.path.isdir() and also verify required files (e.g., key
manifests used by run_static_checks) exist; on failure print a structured,
human-readable error message referencing args.pc_repo, exit with a non-zero
status, and only call run_static_checks when all checks pass.
- Around line 57-72: The current direct uses of split(...)[1] for
greeting_block, capsule_block, legacy_externalized_intro_block, and
greeting_play_block are fragile and raise IndexError if the marker strings in
day1 or director change; create and use a safe_split_or_empty(text, sep,
count=1) helper that returns '' when the separator/segment isn’t found (instead
of raising) and replace the four split(...)[1].split(...) chains with calls to
this helper so missing boundaries yield empty blocks and let downstream checks
mark FAIL rather than crash.
- Around line 575-582: 捕获 node 不存在导致的 FileNotFoundError:把对 subprocess.run(...)
生成的 `completed` 变量包在 try/except FileNotFoundError 里(定位到脚本中创建 `completed`
的代码块),如果捕获到 FileNotFoundError,构造一个与 subprocess.run 返回类型兼容的失败结果(例如使用
subprocess.CompletedProcess(args, returncode=127, stdout="", stderr=str(e)
或者相应字符串),并将其赋给 `completed`,这样后续基于 `completed.returncode`/`completed.stderr`
的检查和报告逻辑(脚本后续 `_check`/报告生成)能继续运行而不是抛出异常。
In `@static/app-interpage.js`:
- Around line 2229-2243: The message handler registered in
window.addEventListener('message', ...) currently only checks
data.__nekoTutorialOverlayRelay and must also enforce same-origin; before
reading data.payload or calling
shouldBypassYuiGuideMessageDedup/isDuplicateMessage/handleYuiGuideRelayedMessage,
reject events where event.origin !== window.location.origin (return early) so
only same-origin postMessage is accepted, mirroring the other postMessage entry
points in this file.
- Around line 51-62: 在 shouldBypassYuiGuideMessageDedup 函数中移除
'yui_guide_rotate_compact_tool_wheel' 这个 action(当前将其列入白名单导致绕过重复判定并在双通道到达时重复执行
rotateCompactToolWheel),也可以作为替代方案实现基于稳定标识的去重(例如使用 messageId 或
tutorialRunId+sequence)并在接收分支(BroadcastChannel/__nekoTutorialOverlayRelay)处检查该标识避免重复执行;定位到函数
shouldBypassYuiGuideMessageDedup 并删掉该 action 或加入去重校验以修复重复执行问题。
In `@static/app-react-chat-window.js`:
- Around line 2112-2143: Two regressions: (1) tutorial input lock isn't
synchronized at startup — ensure init() sets the tutorial input lock state
immediately instead of relying only on events by calling
setHomeTutorialInputLocked(true) when you detect the home tutorial is running
(update the init/startup code that currently checks tutorial state around
compactInputLocked). (2) assistant avatar preview overwrites original source —
add a stable baseAvatarUrl (e.g., preserve original message.avatarUrl into
message.baseAvatarUrl when normalizing messages in the function that builds
messages) and change refreshAssistantAvatarUrls and
resolveCurrentAssistantAvatarUrl to compute the shown avatar from baseAvatarUrl
+ current preview (never clobber baseAvatarUrl), so clearing preview can restore
the original server avatar.
In `@static/avatar-floating-guide-reset.js`:
- Around line 647-653: The fallback branch is double-invoking the icebreaker
reset because resetGuideRoundState(round, options) already calls
resetIcebreakerDay(round); modify the logic so resetIcebreakerDay(round) is only
called when using the manager path: call
manager.resetAvatarFloatingGuideRoundState(round, options) and then call
resetIcebreakerDay(round) in that branch, but remove the unconditional
resetIcebreakerDay(round) after the if/else so the fallback that calls
resetGuideRoundState does not trigger a second reset.
In `@static/css/index.css`:
- Around line 154-170: 新增的可交互控件(例如 .home-tutorial-reset-controls 和 另一个在 diff
213-227 的引导控件)会被 body.neko-game-active 的隐藏列表遗漏,导致 elementFromPoint
命中并阻挡小游戏点击;在现有 body.neko-game-active CSS 规则中把这两个新控件加入隐藏/不可交互的选择器(例如
body.neko-game-active .home-tutorial-reset-controls 和 body.neko-game-active
.<第二个控件类名>),并在该规则中设置 display: none (或 visibility: hidden) 和/或 pointer-events:
none(加上 !important 如当前样式使用了高优先级),以确保在 neko-game-active 时这些控件不会拦截点击。
---
Nitpick comments:
In `@docs/design/avatar-floating-guide-feature-tree.md`:
- Around line 359-394: The doc lacks an implementation/tracking plan for the
"待补能力" (soft-lock, input hint, toolbar auto-switch, lollipop count unlock and
new state machine/handlers); add a short implementation section listing (a)
whether each capability will be delivered in follow-up independent PRs or
included in this PR, (b) milestones/timeline or Jira/Ticket IDs for each item,
and (c) confirmation whether Day 1-7 main flow can fully run without these
features (and what graceful degradations are used—e.g., reuse existing
composerDisabled/home tutorial interaction lock and fallback UX), referencing
the terms "软锁", "composerDisabled", "home tutorial interaction lock", and
"棒棒糖计数解锁" so reviewers can verify scope and follow-up work.
In `@frontend/react-neko-chat/src/message-schema.ts`:
- Around line 170-176: The schemas use unexplained magic numbers: clarify and
centralize them by exporting a named constant (e.g.,
COMPACT_TOOL_WHEEL_POSITIONS) and replacing hard-coded bounds in
compactToolWheelRotateRequestSchema and compactToolWheelIndexRequestSchema with
that constant (use COMPACT_TOOL_WHEEL_POSITIONS for stepCount.max and
COMPACT_TOOL_WHEEL_POSITIONS - 1 for index.max), and add a short inline comment
on the direction union in compactToolWheelRotateRequestSchema (e.g., 1:
clockwise, -1: counter-clockwise) so the intent and the source of the numeric
limits are explicit and easy to update.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6347ff48-c2ba-4f99-9ed9-741211b6a57d
⛔ Files ignored due to path filters (8)
static/assets/tutorial/guide-audio/zh/人类!你真的很没礼貌.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/你看,这里可以穿我的.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/好啦好啦,不霸占你的.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/当然啦,如果你想让本.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/微风、阳光,还有刚刚.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/有了它们,我不光能看.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/等一下啦!还没结束呢.mp3is excluded by!**/*.mp3static/assets/tutorial/guide-audio/zh/还没完呢!你快看快看.mp3is excluded by!**/*.mp3
📒 Files selected for processing (92)
docs/design/avatar-floating-7day-complete-guide-dev.mddocs/design/avatar-floating-day1-home-guide-dev.mddocs/design/avatar-floating-day2-screen-voice-guide-dev.mddocs/design/avatar-floating-day3-agent-guide-dev.mddocs/design/avatar-floating-day4-companion-guide-dev.mddocs/design/avatar-floating-day5-personalization-guide-dev.mddocs/design/avatar-floating-day6-agent-guide-dev.mddocs/design/avatar-floating-day7-graduation-guide-dev.mddocs/design/avatar-floating-guide-feature-tree.mddocs/design/avatar-floating-panel-functions.mddocs/design/avatar-floating-pc-global-overlay-migration-plan.mddocs/design/avatar-floating-post-theater-chat-branches.mddocs/design/avatar-performance-module-maintenance.mddocs/design/day1-icebreaker-dialogue-tree.htmldocs/design/home-tutorial-yui-guide-performance-owner-stage-breakdown.mddocs/design/home-yui-guide-day1-dialogue-flow.mddocs/design/home-yui-guide-lifecycle-modularization.mddocs/design/home-yui-guide-text-highlight-cursor-flow.mddocs/design/new-user-icebreaker-handoff.mddocs/design/software-function-inventory-and-guide-gap-check.mdfrontend/plugin-manager/src/components/plugin/MarketPanel.vuefrontend/plugin-manager/src/yui-guide-runtime.tsfrontend/react-neko-chat/src/App.test.tsxfrontend/react-neko-chat/src/App.tsxfrontend/react-neko-chat/src/message-schema.test.tsfrontend/react-neko-chat/src/message-schema.tsmain_logic/core.pymain_routers/game_router.pymain_routers/pages_router.pymain_routers/websocket_router.pyplugin/plugins/galgame_plugin/screen_classifier.pyscripts/capture_day3_avatar_tools_capsule_frames.pyscripts/diagnose_ghost_cursor_resistance.pyscripts/diagnose_pc_capsule_spotlight.pyscripts/diagnose_pc_day1_capsule_wobble.pyscripts/monitor_pc_day1_intro_greeting_cursor.pyscripts/monitor_pc_day3_avatar_tools.pystatic/app-buttons.jsstatic/app-interpage.jsstatic/app-proactive.jsstatic/app-react-chat-window.jsstatic/app-tutorial-prompt.jsstatic/app-websocket.jsstatic/avatar-floating-guide-reset.jsstatic/css/index.cssstatic/css/yui-guide.cssstatic/icebreaker/icebreaker_scripts.jsonstatic/icebreaker/locales/en.jsonstatic/icebreaker/locales/es.jsonstatic/icebreaker/locales/ja.jsonstatic/icebreaker/locales/ko.jsonstatic/icebreaker/locales/pt.jsonstatic/icebreaker/locales/ru.jsonstatic/icebreaker/locales/zh-CN.jsonstatic/icebreaker/locales/zh-TW.jsonstatic/icebreaker/new-user-icebreaker.jsstatic/locales/en.jsonstatic/locales/es.jsonstatic/locales/ja.jsonstatic/locales/ko.jsonstatic/locales/pt.jsonstatic/locales/ru.jsonstatic/locales/zh-CN.jsonstatic/locales/zh-TW.jsonstatic/tutorial-highlight-controller.jsstatic/tutorial-interaction-takeover.jsstatic/tutorial-interrupt-controller.jsstatic/universal-tutorial-manager.jsstatic/yui-guide-avatar-stage.jsstatic/yui-guide-day1-home-guide.jsstatic/yui-guide-day2-screen-voice-guide.jsstatic/yui-guide-day3-interaction-guide.jsstatic/yui-guide-day4-companion-guide.jsstatic/yui-guide-day5-personalization-guide.jsstatic/yui-guide-day6-agent-guide.jsstatic/yui-guide-day7-graduation-guide.jsstatic/yui-guide-director.jsstatic/yui-guide-overlay.jsstatic/yui-guide-steps.jstemplates/api_key_settings.htmltemplates/chat.htmltemplates/index.htmltemplates/memory_browser.htmltests/frontend/test_home_prompt_flow.pytests/test_agent_rewrite_regression.pytests/unit/test_app_websocket_static.pytests/unit/test_avatar_floating_day1_round_contracts.pytests/unit/test_game_router.pytests/unit/test_new_user_icebreaker_context.pytests/unit/test_new_user_icebreaker_static_contracts.pytests/unit/test_react_chat_window_static.pytests/unit/test_websocket_home_tutorial_guard.py
💤 Files with no reviewable changes (1)
- plugin/plugins/galgame_plugin/screen_classifier.py
| body.yui-taking-over.yui-user-cursor-revealed #${ROOT_ID} .yui-guide-plugin-spotlight, { | ||
| cursor: auto !important; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/plugin-manager/src/yui-guide-runtime.ts"
echo "== around injectStyle selector (~line 830-870) =="
nl -ba "$file" | sed -n '820,870p'
echo
echo "== definitions around hasDesktopTutorialSkipBridge / interrupt bridge capability (~lines 120-200, if present) =="
nl -ba "$file" | sed -n '110,200p' || true
echo
echo "== around handleInterrupt (~line 2500-2605) =="
nl -ba "$file" | sed -n '2480,2620p'
echo
echo "== search for desktop interrupt/skip capability helpers in this file =="
rg -n "hasDesktopTutorial.*Bridge|requestTutorialInterrupt|requestTutorialSkip|handleInterrupt" "$file"Repository: Project-N-E-K-O/N.E.K.O
Length of output: 178
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/plugin-manager/src/yui-guide-runtime.ts"
echo "== around injectStyle selector (~line 820-870) =="
sed -n '820,870p' "$file" | cat -n -w3 -s $'\t'
echo
echo "== definitions around hasDesktopTutorialSkipBridge / interrupt bridge capability (~lines 110-220) =="
sed -n '110,220p' "$file" | cat -n -w3 -s $'\t' || true
echo
echo "== around handleInterrupt (~line 2480-2620) =="
sed -n '2480,2620p' "$file" | cat -n -w3 -s $'\t'
echo
echo "== search for desktop interrupt/skip capability helpers in this file =="
rg -n "hasDesktopTutorial.*Bridge|requestTutorialInterrupt|requestTutorialSkip|handleInterrupt" "$file"Repository: Project-N-E-K-O/N.E.K.O
Length of output: 201
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/plugin-manager/src/yui-guide-runtime.ts"
echo "== around injectStyle selector (~line 820-870) =="
awk 'NR>=820 && NR<=870 {printf "%d\t%s\n", NR, $0}' "$file" || true
echo
echo "== definitions around hasDesktopTutorialSkipBridge / interrupt bridge capability (~lines 110-220) =="
awk 'NR>=110 && NR<=220 {printf "%d\t%s\n", NR, $0}' "$file" || true
echo
echo "== around handleInterrupt (~line 2480-2620) =="
awk 'NR>=2480 && NR<=2620 {printf "%d\t%s\n", NR, $0}' "$file" || true
echo
echo "== search for desktop interrupt/skip capability helpers in this file =="
rg -n "hasDesktopTutorialSkipBridge|hasDesktop.*Interrupt|requestTutorialInterrupt|requestTutorialSkip|handleInterrupt" "$file" || trueRepository: Project-N-E-K-O/N.E.K.O
Length of output: 13655
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/plugin-manager/src/yui-guide-runtime.ts"
echo "== around hasDesktopTutorialSkipBridge / hasDesktopTutorialInterruptBridge (line ~1320-1370) =="
sed -n '1315,1375p' "$file" | cat -n -w3 -s $'\t'
echo
echo "== around desktop skip request usage (~1370-1445) =="
sed -n '1368,1450p' "$file" | cat -n -w3 -s $'\t'
echo
echo "== around desktop interrupt request usage (~1580-1655) =="
sed -n '1570,1660p' "$file" | cat -n -w3 -s $'\t'
echo
echo "== around handleInterruptAck / desktop runtime interactions (~1480-1625) =="
sed -n '1470,1635p' "$file" | cat -n -w3 -s $'\t'Repository: Project-N-E-K-O/N.E.K.O
Length of output: 247
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/plugin-manager/src/yui-guide-runtime.ts"
echo "== around hasDesktopTutorialSkipBridge / hasDesktopTutorialInterruptBridge (line ~1315-1375) =="
awk 'NR>=1315 && NR<=1375 {print NR "\t" $0}' "$file" || true
echo
echo "== around desktop skip request usage (~1370-1450) =="
awk 'NR>=1368 && NR<=1455 {print NR "\t" $0}' "$file" || true
echo
echo "== around desktop interrupt request usage (~1570-1660) =="
awk 'NR>=1570 && NR<=1665 {print NR "\t" $0}' "$file" || true
echo
echo "== around handleInterruptAck / desktop runtime interactions (~1470-1635) =="
awk 'NR>=1470 && NR<=1638 {print NR "\t" $0}' "$file" || trueRepository: Project-N-E-K-O/N.E.K.O
Length of output: 14714
修掉 yui-guide-runtime 的 CSS 选择器语法 + 修正 handleInterrupt 的桌面桥能力判断喵
-
frontend/plugin-manager/src/yui-guide-runtime.ts注入样式里body...#${ROOT_ID} .yui-guide-plugin-spotlight, {末尾多了逗号,导致该规则选择器语法非法,浏览器会整条丢弃;删掉逗号即可喵🩹 建议 diff
-
body.yui-taking-over.yui-user-cursor-revealed #${ROOT_ID} .yui-guide-plugin-spotlight, {
- body.yui-taking-over.yui-user-cursor-revealed #${ROOT_ID} .yui-guide-plugin-spotlight {
cursor: auto !important;
}
</details>
- `handleInterrupt()` 里绕过 `document.hasFocus()` 的条件用的是 `!this.hasDesktopTutorialSkipBridge()`,但桌面中断能力在代码里是拆成 `requestTutorialInterrupt` 的(`hasDesktopTutorialInterruptBridge()` / `canRequestHomeInterruptPlayback()`);这里应改为基于 `hasDesktopTutorialInterruptBridge()`(或同一套“是否可发中断”能力)来判断喵
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
body.yui-taking-over.yui-user-cursor-revealed #${ROOT_ID} .yui-guide-plugin-spotlight {
cursor: auto !important;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/plugin-manager/src/yui-guide-runtime.ts` around lines 854 - 855, The
injected stylesheet in yui-guide-runtime contains an invalid selector trailing
comma ("body.yui-taking-over.yui-user-cursor-revealed #${ROOT_ID}
.yui-guide-plugin-spotlight, {") — remove the extra comma so the selector is
valid (reference ROOT_ID and .yui-guide-plugin-spotlight in the injected CSS);
and in handleInterrupt() replace the incorrect bridge check using
!this.hasDesktopTutorialSkipBridge() with the proper desktop interrupt
capability check (use this.hasDesktopTutorialInterruptBridge() or
this.canRequestHomeInterruptPlayback() which corresponds to
requestTutorialInterrupt) so the logic correctly detects when the desktop can
accept tutorial interrupt requests.
- 补齐 Day3 工具菜单状态同步、点击态与监控脚本取样逻辑 - 修复教程消息来源校验、锁定态提交保护、结束状态持久化与模型切换失败恢复 - 增强 Day1/Day3 诊断脚本预检和报告稳定性 - 同步 Day2-Day6 与生命周期文档说明 - 音频文件缺失相关 review 按要求未处理
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fc2cb1be8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| function audioFilesForAllLocales(fileName) { | ||
| return Object.freeze({ | ||
| zh: '', |
There was a problem hiding this comment.
Restore Chinese guide audio mapping
For users whose locale resolves to zh, every Day 1 guide voice key now has a zh entry whose value is the empty string; guideAudioSrc() treats the presence of that locale key as authoritative, so it returns '' instead of falling back to the bundled audio. This makes the Chinese onboarding narration silently wait rather than play audio for all Day 1 scenes that use audioFilesForAllLocales.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
static/app-tutorial-prompt.js (1)
525-551:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift这个 enforce 流程有两个隐患,可能导致 agent 状态错乱喵!
问题 1:Line 532 的并发竞态(!suppression.active 分支)
Line 531 的
beginHomeTutorialFeatureSuppression里已经在 line 518 用void snapshotAndDisableAgentFlags(token)触发了完整的 agent 抑制流程喵。然后 line 532 又立即void forceDisableAgentForTutorial(...),两个 async 操作并行跑,会导致:
snapshotAndDisableAgentFlags正在读取 flags snapshot 时,forceDisableAgentForTutorial已经把 agent 关了 → snapshot 捕获的不是"抑制前"的真实状态- 同一个
/api/agent/command和/api/agent/flagsendpoint 在极短时间内收到多次 POST → 后端可能出现竞态- 如果 snapshot 流程失败,restore 时会恢复错误的状态
问题 2:缺少 token 检查(两个分支都有)
forceDisableAgentForTutorial是 async 的,但里面没有任何 token 检查喵。如果在 await 期间 suppression 被结束了(比如用户跳过教程),这个函数仍会继续执行,把已经 restore 的 agent 重新关掉喵。参考既有的
snapshotAndDisableAgentFlags(lines 435-476),每个 await 后都检查:if (!state.featureSuppression.active || state.featureSuppression.token !== token) { return; // 或者尝试 restore }建议修法喵:
参考 past review comment 的建议,抽一个带 token 检查的 helper:
async function reapplySuppressedAgentFlags(token, reason) { if (!state.featureSuppression.active || state.featureSuppression.token !== token) { return; } try { await postAgentCommand('set_agent_enabled', { enabled: false }); if (!state.featureSuppression.active || state.featureSuppression.token !== token) { return; } await postAgentFlags(buildDisabledAgentChildFlags()); if (!state.featureSuppression.active || state.featureSuppression.token !== token) { return; } syncAgentFlagsUi(); } catch (error) { console.warn('[TutorialPrompt] failed to re-apply agent suppression:', reason || '', error); } }然后改成:
function enforceHomeTutorialFeatureSuppression(reason) { // ... if (!suppression.active) { beginHomeTutorialFeatureSuppression(reason || 'tutorial-enforced'); - void forceDisableAgentForTutorial(reason || 'tutorial-enforced'); + // beginHomeTutorialFeatureSuppression 里的 snapshotAndDisableAgentFlags 已经处理了 agent return; } setGalgameState(false); const proactiveOff = {}; HOME_TUTORIAL_PROACTIVE_KEYS.forEach(function (key) { proactiveOff[key] = false; }); applyProactiveState(proactiveOff); - void forceDisableAgentForTutorial(reason || (suppression.snapshot && suppression.snapshot.reason) || 'tutorial-enforced'); + void reapplySuppressedAgentFlags(suppression.token, reason || (suppression.snapshot && suppression.snapshot.reason) || 'tutorial-enforced'); window.dispatchEvent(new CustomEvent('neko:home-tutorial-features-suppressed', { // ... })); }这样既避免了 line 532 的竞态,又在 line 542 加上了 token 检查,和既有的 snapshot/restore 流程保持一致喵!
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/app-tutorial-prompt.js` around lines 525 - 551, enforceHomeTutorialFeatureSuppression currently starts two concurrent async suppression flows (beginHomeTutorialFeatureSuppression -> snapshotAndDisableAgentFlags and a separate forceDisableAgentForTutorial) and calls forceDisableAgentForTutorial without any token checks, causing race conditions and stale restores; fix by creating a token-aware helper (e.g. reapplySuppressedAgentFlags) that mirrors snapshotAndDisableAgentFlags' post-await token checks and does set_agent_enabled + postAgentFlags + syncAgentFlagsUi, then replace the direct void forceDisableAgentForTutorial(...) calls in enforceHomeTutorialFeatureSuppression with either awaiting beginHomeTutorialFeatureSuppression or invoking the new reapplySuppressedAgentFlags(token, reason) so you never run two suppression flows in parallel and ensure every await in forceDisableAgentForTutorial/reapplySuppressedAgentFlags verifies state.featureSuppression.active and state.featureSuppression.token before proceeding.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/design/avatar-floating-day3-agent-guide-dev.md`:
- Line 16: 将当前表格外的段落(描述 `day3_avatar_tools_props` 前置条件:需要弧形工具菜单保持 open,React 条件为
`toolMenuOpen && compactInputToolFanOpen`,不能只调用
`setAvatarToolMenuOpen(true)`,如需重新打开应通过 host request 或
`openCompactInputToolFan(..., { ignoreDisabled: true })` 再同步 Avatar 道具菜单)合并回
Scene 3 的“高光与 Ghost Cursor”表格单元格中,以修复渲染和 markdownlint(MD055/MD056)错误;确保保留并使用了符号
`day3_avatar_tools_props`、`toolMenuOpen`、`compactInputToolFanOpen`、`setAvatarToolMenuOpen(true)`
和 `openCompactInputToolFan(..., { ignoreDisabled: true })` 来保持语义准确。
In `@static/app-interpage.js`:
- Around line 423-482: The rollback helper
restorePreviousModelUiAfterFailedSwitch currently re-shows the old model UI but
does not undo the failed target container/canvas state and wrongly calls
clearMMDCanvasLoadingSession() (which hides/retires the canvas) when restoring
MMD; update restorePreviousModelUiAfterFailedSwitch to explicitly remove any
loading session marker on the failed mmdCanvas (clear the session id/attribute
used for MMD loading), restore/override display/visibility/pointerEvents on the
failed target container/canvas to a hidden or neutral state, and remove the
clearMMDCanvasLoadingSession(mmdCanvas) call; ensure similar hiding/cleanup is
done for the VRM/live2d/live3d target canvases so the failed blank canvas is not
left visible, and still call window.showCurrentModel() if available.
In `@static/app-tutorial-prompt.js`:
- Around line 483-491: forceDisableAgentForTutorial is sending { agent_enabled:
false } to postAgentFlags which violates the established API contract where
postAgentFlags only accepts child flags (agent_enabled is the master flag set
via postAgentCommand); update forceDisableAgentForTutorial to call
postAgentCommand('set_agent_enabled', { enabled: false }) for the master toggle
and pass child-only flags to postAgentFlags (e.g., call
postAgentFlags(buildDisabledAgentChildFlags()) or otherwise ensure the object
excludes agent_enabled) and then call syncAgentFlagsUi(), keeping existing
helpers buildDisabledAgentChildFlags and buildAgentChildFlags as the canonical
source of child flags.
---
Outside diff comments:
In `@static/app-tutorial-prompt.js`:
- Around line 525-551: enforceHomeTutorialFeatureSuppression currently starts
two concurrent async suppression flows (beginHomeTutorialFeatureSuppression ->
snapshotAndDisableAgentFlags and a separate forceDisableAgentForTutorial) and
calls forceDisableAgentForTutorial without any token checks, causing race
conditions and stale restores; fix by creating a token-aware helper (e.g.
reapplySuppressedAgentFlags) that mirrors snapshotAndDisableAgentFlags'
post-await token checks and does set_agent_enabled + postAgentFlags +
syncAgentFlagsUi, then replace the direct void forceDisableAgentForTutorial(...)
calls in enforceHomeTutorialFeatureSuppression with either awaiting
beginHomeTutorialFeatureSuppression or invoking the new
reapplySuppressedAgentFlags(token, reason) so you never run two suppression
flows in parallel and ensure every await in
forceDisableAgentForTutorial/reapplySuppressedAgentFlags verifies
state.featureSuppression.active and state.featureSuppression.token before
proceeding.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9cfc3461-238d-4151-9a38-f2e56bea3bfa
📒 Files selected for processing (21)
docs/design/avatar-floating-day2-screen-voice-guide-dev.mddocs/design/avatar-floating-day3-agent-guide-dev.mddocs/design/avatar-floating-day4-companion-guide-dev.mddocs/design/avatar-floating-day5-personalization-guide-dev.mddocs/design/avatar-floating-day6-agent-guide-dev.mddocs/design/home-yui-guide-lifecycle-modularization.mdfrontend/plugin-manager/src/yui-guide-runtime.tsfrontend/react-neko-chat/src/App.test.tsxfrontend/react-neko-chat/src/App.tsxmain_routers/game_router.pyscripts/diagnose_pc_day1_capsule_wobble.pyscripts/monitor_pc_day3_avatar_tools.pystatic/app-buttons.jsstatic/app-interpage.jsstatic/app-proactive.jsstatic/app-react-chat-window.jsstatic/app-tutorial-prompt.jsstatic/app-websocket.jsstatic/avatar-floating-guide-reset.jsstatic/css/index.cssstatic/yui-guide-director.js
✅ Files skipped from review due to trivial changes (3)
- docs/design/avatar-floating-day2-screen-voice-guide-dev.md
- docs/design/avatar-floating-day4-companion-guide-dev.md
- docs/design/avatar-floating-day5-personalization-guide-dev.md
🚧 Files skipped from review as they are similar to previous changes (11)
- static/app-buttons.js
- static/css/index.css
- docs/design/avatar-floating-day6-agent-guide-dev.md
- scripts/monitor_pc_day3_avatar_tools.py
- frontend/react-neko-chat/src/App.test.tsx
- docs/design/home-yui-guide-lifecycle-modularization.md
- static/app-react-chat-window.js
- static/avatar-floating-guide-reset.js
- frontend/react-neko-chat/src/App.tsx
- scripts/diagnose_pc_day1_capsule_wobble.py
- frontend/plugin-manager/src/yui-guide-runtime.ts
- 拉取并合并 Project-N-E-K-O/N.E.K.O main - 解决 tests/unit/test_react_chat_window_static.py 冲突,保留双方新增静态测试 - pytest 本机未安装,已通过 py_compile 和 git diff --check 基础验证
- 修复插件页 interrupt bridge 判断、compact fan 请求去重时机和轮盘重复执行 - 修复聊天窗头像预览恢复、教程输入锁启动同步、icebreaker reset 双调用和小游戏态遮挡 - 补齐资源版本 locale glob、诊断脚本预检和未实现检查 - 更新文档路径与待补能力计划,并抽出 compact tool wheel 常量 - 音频文件缺失相关 review 按要求未处理
- 将 Day3 Avatar 工具前置条件合并回表格单元格 - 修复模型切换失败回滚时失败目标 canvas 残留与 MMD loading 标记清理 - 修复 Agent suppression 的 master/child flag API 使用和 token-aware 串行压制
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db3391a5a1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
|
|
||
| function isNewUserIcebreakerBlockingGreeting(reason) { | ||
| if (isNewUserIcebreakerPeriodActive()) return true; |
There was a problem hiding this comment.
Clear pending greetings during icebreaker suppression
When any icebreaker day has been started or completed but day 7 is not complete, this returns true for the entire multi-day onboarding period. _sendGreetingCheckIfReady() treats that as a temporary blocker, keeps S._greetingCheckPending set, and reschedules itself with the retry timer (capped at 5s), so users who complete/skip the first tutorial keep sending home_tutorial_state and running the greeting retry loop until day 7 finishes; if the goal is to suppress the greeting while the icebreaker owns the slot, consume/clear the pending greeting instead of reporting a blocker for days.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
static/avatar-floating-guide-reset.js (2)
631-637:⚠️ Potential issue | 🟠 Major | ⚡ Quick win重置主路径漏掉了 home reset 同步与本地广播分发喵。
现在
resetHomeTutorialFallback()没有被调用,且 manager 分支不会走dispatchGuideResetEvent(),会让跨标签页/跨模块的重置监听链条失效喵。💡 建议补丁
async function resetHomeTutorialDay(day, options = {}) { const round = normalizeRound(day); let state = null; + const source = options.source || 'home_reset_button'; const manager = window.universalTutorialManager || null; if (manager && typeof manager.resetAvatarFloatingGuideRoundState === 'function') { state = manager.resetAvatarFloatingGuideRoundState(round, options); resetIcebreakerDay(round); + const resetAt = new Date().toISOString(); + dispatchGuideResetEvent({ day: round, source, resetAt, state }); } else { state = resetGuideRoundState(round, options); } + await resetHomeTutorialFallback(); - await startAvatarFloatingGuideDay(round, { source: options.source || 'home_reset_button' }); + await startAvatarFloatingGuideDay(round, { source }); showResetToast(round); return state; }Also applies to: 639-653, 588-600
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar-floating-guide-reset.js` around lines 631 - 637, The resetHomeTutorialFallback function currently only mutates localStorage and emits a local CustomEvent but is never invoked and the manager branch doesn't call dispatchGuideResetEvent, breaking cross-tab/module reset propagation; update the code so resetHomeTutorialFallback is invoked from the manager reset path and/or replace its internal CustomEvent dispatch with or also call the shared dispatchGuideResetEvent helper (and ensure HOME_MANUAL_INTENT_KEY and HOME_TUTORIAL_KEYS changes remain) so resets trigger the centralized broadcast logic used for cross-tab listeners; locate resetHomeTutorialFallback and the manager reset branch and add the call to dispatchGuideResetEvent(...) (keeping the same detail payload) or call resetHomeTutorialFallback from where dispatchGuideResetEvent would run to preserve existing behavior.
571-574:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
skip结算和universal-tutorial-manager的 day1 语义分叉了喵。fallback 的
markGuideRoundOutcome()在skip时总是移除 completed;但 manager 契约里 day1 skip 会保留 completed。两套实现会在 manager 缺失时写出不同状态喵。💡 建议补丁
} else if (outcome === 'skip') { state.skippedRounds = normalizeRoundList(state.skippedRounds.concat(round)); - state.completedRounds = omitRound(state.completedRounds, round); + state.completedRounds = round === 1 + ? normalizeRoundList(state.completedRounds.concat(round)) + : omitRound(state.completedRounds, round); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar-floating-guide-reset.js` around lines 571 - 574, The fallback markGuideRoundOutcome currently treats outcome === 'skip' by always adding to state.skippedRounds and removing from state.completedRounds via normalizeRoundList and omitRound, but this diverges from universal-tutorial-manager which preserves completed for day1 skips; change markGuideRoundOutcome so that for 'skip' it preserves completedRounds when the skipped round is a day1 semantic (use the same predicate the manager uses — e.g., isDayOneRound(round) or round.meta.day === 1), only calling omitRound(state.completedRounds, round) when the round is not day1, and still updating state.skippedRounds with normalizeRoundList; ensure references to markGuideRoundOutcome, normalizeRoundList, omitRound, state.skippedRounds and state.completedRounds are updated accordingly.
♻️ Duplicate comments (1)
static/avatar-floating-guide-reset.js (1)
656-663:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
detectModelPrefix()把live3d映射成了不存在的 DOM 前缀喵。这里返回
live3d会把后续#${prefix}-...选择器拼成#live3d-*,和现有vrm-*节点命名不一致,兜底路径会稳定 miss 喵。💡 建议补丁
function detectModelPrefix() { if (document.getElementById('vrm-floating-buttons')) return 'vrm'; if (document.getElementById('mmd-floating-buttons')) return 'mmd'; if (document.getElementById('live2d-floating-buttons')) return 'live2d'; const cfg = window.lanlan_config && window.lanlan_config.model_type; - if (cfg === 'live3d' || cfg === 'vrm') return 'live3d'; + if (cfg === 'live3d' || cfg === 'vrm') return 'vrm'; if (cfg === 'mmd') return 'mmd'; return 'live2d'; }Based on learnings: 本仓库会把
model_type从vrm归一化为live3d,前端分支仍需映射到正确的 VRM 运行时路径喵。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/avatar-floating-guide-reset.js` around lines 656 - 663, detectModelPrefix() returns 'live3d' for cfg values 'live3d' or 'vrm', which produces selectors like '`#live3d-`*' that don't match existing DOM ids; update detectModelPrefix so that when window.lanlan_config.model_type is 'live3d' or 'vrm' it returns 'vrm' (not 'live3d'), keeping the initial DOM-id checks for 'vrm-floating-buttons', 'mmd-floating-buttons', and 'live2d-floating-buttons' and preserving the existing fallbacks for 'mmd' and 'live2d'.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/design/avatar-floating-guide-feature-tree.md`:
- Line 366: The heading level jumps from "##" to "####" which triggers
markdownlint MD001; change the heading "#### 待补能力实现与跟踪计划" to use "###
待补能力实现与跟踪计划" so the header sequence is consistent and the lint error is
resolved.
---
Outside diff comments:
In `@static/avatar-floating-guide-reset.js`:
- Around line 631-637: The resetHomeTutorialFallback function currently only
mutates localStorage and emits a local CustomEvent but is never invoked and the
manager branch doesn't call dispatchGuideResetEvent, breaking cross-tab/module
reset propagation; update the code so resetHomeTutorialFallback is invoked from
the manager reset path and/or replace its internal CustomEvent dispatch with or
also call the shared dispatchGuideResetEvent helper (and ensure
HOME_MANUAL_INTENT_KEY and HOME_TUTORIAL_KEYS changes remain) so resets trigger
the centralized broadcast logic used for cross-tab listeners; locate
resetHomeTutorialFallback and the manager reset branch and add the call to
dispatchGuideResetEvent(...) (keeping the same detail payload) or call
resetHomeTutorialFallback from where dispatchGuideResetEvent would run to
preserve existing behavior.
- Around line 571-574: The fallback markGuideRoundOutcome currently treats
outcome === 'skip' by always adding to state.skippedRounds and removing from
state.completedRounds via normalizeRoundList and omitRound, but this diverges
from universal-tutorial-manager which preserves completed for day1 skips; change
markGuideRoundOutcome so that for 'skip' it preserves completedRounds when the
skipped round is a day1 semantic (use the same predicate the manager uses —
e.g., isDayOneRound(round) or round.meta.day === 1), only calling
omitRound(state.completedRounds, round) when the round is not day1, and still
updating state.skippedRounds with normalizeRoundList; ensure references to
markGuideRoundOutcome, normalizeRoundList, omitRound, state.skippedRounds and
state.completedRounds are updated accordingly.
---
Duplicate comments:
In `@static/avatar-floating-guide-reset.js`:
- Around line 656-663: detectModelPrefix() returns 'live3d' for cfg values
'live3d' or 'vrm', which produces selectors like '`#live3d-`*' that don't match
existing DOM ids; update detectModelPrefix so that when
window.lanlan_config.model_type is 'live3d' or 'vrm' it returns 'vrm' (not
'live3d'), keeping the initial DOM-id checks for 'vrm-floating-buttons',
'mmd-floating-buttons', and 'live2d-floating-buttons' and preserving the
existing fallbacks for 'mmd' and 'live2d'.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 40c7cd69-db51-4d16-83ef-c06fe6398129
📒 Files selected for processing (15)
docs/design/avatar-floating-7day-complete-guide-dev.mddocs/design/avatar-floating-guide-feature-tree.mdfrontend/plugin-manager/src/yui-guide-runtime.tsfrontend/react-neko-chat/src/App.test.tsxfrontend/react-neko-chat/src/App.tsxfrontend/react-neko-chat/src/message-schema.tsmain_routers/game_router.pymain_routers/pages_router.pyscripts/diagnose_pc_capsule_spotlight.pyscripts/diagnose_pc_day1_capsule_wobble.pystatic/app-interpage.jsstatic/app-react-chat-window.jsstatic/avatar-floating-guide-reset.jsstatic/css/index.csstests/unit/test_react_chat_window_static.py
💤 Files with no reviewable changes (1)
- static/app-interpage.js
🚧 Files skipped from review as they are similar to previous changes (9)
- frontend/react-neko-chat/src/message-schema.ts
- main_routers/game_router.py
- scripts/diagnose_pc_capsule_spotlight.py
- docs/design/avatar-floating-7day-complete-guide-dev.md
- scripts/diagnose_pc_day1_capsule_wobble.py
- frontend/react-neko-chat/src/App.test.tsx
- frontend/react-neko-chat/src/App.tsx
- static/app-react-chat-window.js
- frontend/plugin-manager/src/yui-guide-runtime.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@static/app-tutorial-prompt.js`:
- Around line 489-506: reapplySuppressedAgentFlags is disabling master/child
flags even when snapshot.agentFlags is null, causing irreversible loss; before
calling postAgentCommand('set_agent_enabled', ...) and postAgentFlags(...) (and
before queueing reapply calls from snapshotAndDisableAgentFlags), ensure a valid
snapshot exists: if snapshot.agentFlags is null attempt to fetch and persist a
fresh snapshot (reuse or expose the snapshot-fetch logic from
snapshotAndDisableAgentFlags or call a new ensureSnapshot() helper) and only
proceed to write disabled flags when snapshot.agentFlags is non-null; if
snapshot fetch fails, skip the disable/reapply path and return false to avoid
mutating backend state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3c967510-64aa-4c21-8523-55aed06d67fd
📒 Files selected for processing (3)
docs/design/avatar-floating-day3-agent-guide-dev.mdstatic/app-interpage.jsstatic/app-tutorial-prompt.js
✅ Files skipped from review due to trivial changes (1)
- docs/design/avatar-floating-day3-agent-guide-dev.md
🚧 Files skipped from review as they are similar to previous changes (1)
- static/app-interpage.js
| async function reapplySuppressedAgentFlags(token, reason) { | ||
| try { | ||
| if (!isFeatureSuppressionTokenActive(token)) { | ||
| return false; | ||
| } | ||
| await postAgentCommand('set_agent_enabled', { enabled: false }); | ||
| if (!isFeatureSuppressionTokenActive(token)) { | ||
| return false; | ||
| } | ||
| await postAgentFlags(buildDisabledAgentChildFlags()); | ||
| if (!isFeatureSuppressionTokenActive(token)) { | ||
| return false; | ||
| } | ||
| syncAgentFlagsUi(); | ||
| return true; | ||
| } catch (error) { | ||
| console.warn('[TutorialPrompt] failed to enforce agent suppression:', reason || '', error); | ||
| return false; |
There was a problem hiding this comment.
enforce() 在没有拿到 restore 快照时,还是会把 Agent 后端状态改掉,结束后就还原不回来了喵。
如果首次 snapshotAndDisableAgentFlags() 抓 /api/agent/flags 失败,snapshot.agentFlags 会一直是 null。这时 Line 577-580 还是会排队走 reapplySuppressedAgentFlags(),而 Line 494 和 Line 498 会把 master / child flags 真正写成全关;但后面的恢复路径只在 snapshot.agentFlags 存在时才会执行,所以教程结束后用户原来的 Agent 配置会被静默丢掉喵。
更稳的做法是:重压前先确认快照已经拿到;如果没有,就先补抓并持久化快照,再禁用,或者直接跳过这次 reapply 喵。
🐾 一个可落地的修法示意喵
function queueSuppressedAgentFlagsReapply(token, reason) {
const suppression = state.featureSuppression;
const previous = suppression.agentSuppressionPromise || Promise.resolve();
const next = previous.catch(function () {}).then(function () {
+ if (!isFeatureSuppressionTokenActive(token)) {
+ return false;
+ }
+ if (!suppression.snapshot || !suppression.snapshot.agentFlags) {
+ return snapshotAndDisableAgentFlags(token);
+ }
return reapplySuppressedAgentFlags(token, reason);
});
setAgentSuppressionPromise(next);
}Also applies to: 520-527, 577-580
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@static/app-tutorial-prompt.js` around lines 489 - 506,
reapplySuppressedAgentFlags is disabling master/child flags even when
snapshot.agentFlags is null, causing irreversible loss; before calling
postAgentCommand('set_agent_enabled', ...) and postAgentFlags(...) (and before
queueing reapply calls from snapshotAndDisableAgentFlags), ensure a valid
snapshot exists: if snapshot.agentFlags is null attempt to fetch and persist a
fresh snapshot (reuse or expose the snapshot-fetch logic from
snapshotAndDisableAgentFlags or call a new ensureSnapshot() helper) and only
proceed to write disabled flags when snapshot.agentFlags is non-null; if
snapshot fetch fails, skip the disable/reapply path and return false to avoid
mutating backend state.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6c723845e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| takeover_return_control: Object.freeze({ zh: GUIDE_AUDIO_FILE_NAMES_ZH.takeover_return_control }) | ||
| }); | ||
| const DAY1_HOME_GUIDE = getYuiGuideDailyGuide(1) || {}; | ||
| const GUIDE_AUDIO_FILES_BY_KEY = Object.freeze(Object.assign({}, DAY1_HOME_GUIDE.audioFilesByKey || {})); |
There was a problem hiding this comment.
Load guide audio maps for every day
When Day 2+ avatar-floating rounds run, their scenes use new voice keys such as avatar_floating_day2_intro, but the audio lookup table is built only from DAY1_HOME_GUIDE.audioFilesByKey. Those keys are never registered anywhere else, so guideAudioSrc() returns an empty string for most Day 2-7 narration and the guide falls back to silent waiting instead of playing the narrated audio for those rounds.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
static/app-websocket.js (1)
22-22: 💤 Low value
NEW_USER_ICEBREAKER_STORAGE_KEY和相关函数在app-proactive.js里也有一份喵。本喵注意到
NEW_USER_ICEBREAKER_STORAGE_KEY、readNewUserIcebreakerStore()、isNewUserIcebreakerPeriodActive()在static/app-proactive.js里有几乎一样的实现喵。虽然两个文件都是 IIFE 且可能是为了避免跨模块依赖,但如果后续逻辑有变动,很容易漏改其中一边喵。可以考虑:
- 抽到共享模块(比如
app-icebreaker-utils.js)- 或者至少加个注释标注"需与
app-proactive.js保持同步"不过这个不急,等后面有空再统一也行喵~本喵只是提醒一下而已,才不是关心你呢!
Also applies to: 577-619
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@static/app-websocket.js` at line 22, Duplicate constants and functions (NEW_USER_ICEBREAKER_STORAGE_KEY, readNewUserIcebreakerStore, isNewUserIcebreakerPeriodActive) appear in two places; extract them into a single shared module (e.g., app-icebreaker-utils) and import that from both locations, or if extraction is not feasible now, add a clear synchronized-comment in both files referencing the other file and mark the functions/constants as shared so future changes stay in sync; update references to use the shared names (NEW_USER_ICEBREAKER_STORAGE_KEY, readNewUserIcebreakerStore, isNewUserIcebreakerPeriodActive) to avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@static/app-websocket.js`:
- Line 22: Duplicate constants and functions (NEW_USER_ICEBREAKER_STORAGE_KEY,
readNewUserIcebreakerStore, isNewUserIcebreakerPeriodActive) appear in two
places; extract them into a single shared module (e.g., app-icebreaker-utils)
and import that from both locations, or if extraction is not feasible now, add a
clear synchronized-comment in both files referencing the other file and mark the
functions/constants as shared so future changes stay in sync; update references
to use the shared names (NEW_USER_ICEBREAKER_STORAGE_KEY,
readNewUserIcebreakerStore, isNewUserIcebreakerPeriodActive) to avoid drift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fcbe4e9b-daf6-4fb3-b14f-7e932712d5b4
📒 Files selected for processing (2)
static/app-websocket.jstests/unit/test_app_websocket_static.py
|
@codex review |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17bbfe19f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @@ -85,6 +85,16 @@ | |||
|
|
|||
| <!-- Status 气泡框 --> | |||
| <div id="status-toast"></div> | |||
| <div id="home-tutorial-reset-controls" class="home-tutorial-reset-controls" aria-label="新手教程重置"> | |||
There was a problem hiding this comment.
Hide tutorial reset controls from normal users
This adds the tutorial reset toolbar unconditionally to the production home page; static/css/index.css makes .home-tutorial-reset-controls a fixed visible flex container, so every /index user sees the Chinese “新手教程重置” buttons and can reset/clear onboarding progress. Please gate this behind a dev/debug flag or hide it by default so the new-user flow is not exposed as a permanent production control.
Useful? React with 👍 / 👎.
|
@codex review |
|
@CodeRabbit review |
|
@codex review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17bbfe19f2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| function audioFilesForAllLocales(fileName) { | ||
| return Object.freeze({ | ||
| zh: '', |
There was a problem hiding this comment.
Restore zh audio mapping for Day-1 guide
For Chinese locales, guideAudioSrc() sees the zh property as present and uses its value directly; because this helper now sets zh to an empty string, it returns no audio URL and speak() falls through to a silent wait instead of playing or falling back. Since all Day-1 audioFilesByKey entries use audioFilesForAllLocales(...), default zh/zh-CN users lose the narrated guide audio for the whole Day-1 flow.
Useful? React with 👍 / 👎.
| id: 'day2_intro_context', | ||
| textKey: 'tutorial.avatarFloating.day2.intro', | ||
| voiceKey: 'avatar_floating_day2_intro', |
There was a problem hiding this comment.
Add audio mappings for later-day voice keys
This new Day-2 scene introduces avatar_floating_day2_intro, but the director only builds GUIDE_AUDIO_FILES_BY_KEY from YuiGuideDailyGuides[1].audioFilesByKey; the Day-2 through Day-7 guide files register scenes with new voiceKeys but no audioFilesByKey, so resolveGuideAudioSrc() returns empty and narration silently waits for estimated duration. Users starting the later daily guide rounds will see text/cursor movement without any voice playback for these new lines.
Useful? React with 👍 / 👎.
Summary by CodeRabbit
新特性
文档
国际化