Skip to content

fix: 新版 Codex App 兼容适配(面板边界、顶部入口与注入卡顿缓解) - #2309

Open
Yuimi-chaya wants to merge 2 commits into
BigPizzaV3:mainfrom
Yuimi-chaya:codex/sidebar-panel-stability-20260926
Open

Yuimi-chaya wants to merge 2 commits into
BigPizzaV3:mainfrom
Yuimi-chaya:codex/sidebar-panel-stability-20260926

Conversation

@Yuimi-chaya

@Yuimi-chaya Yuimi-chaya commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

概要

适配新版 Codex App 的标题栏入口与应用内面板边界,并减少重复注入/模型白名单刷新给 renderer 带来的负担。基于 main 的 ab203409,保留上游图标栏、拓展接口、插件过滤与浅色主题修复,与 #2313 的启动/重启生命周期保持独立。

入口与面板

  • Windows HTML 应用菜单后增加 Codex++ 入口;不改原生菜单身份、键盘焦点管理或拖动区域。正常状态放在提示中,后端失败显示角标,不保留没有真实计数来源的标题栏降级分支。
  • 优先使用已有帮助菜单标识;标识变化时以原生 menubar 和最后一个 menuitem 作结构兜底,不依赖菜单文字或语言。
  • 标题栏只替代 Codex++ 主页入口,保留上游拓展/推荐内容入口。先确认替代入口挂载,再清理旧侧栏入口;rail 暂缺时仍同步标题栏选中态。非当前入口移除 aria-current,当前入口使用 page。
  • 面板从 rail 右边界铺开并遵守宿主主内容区顶部,保留上游缩放坐标换算、页面布局与原生选中态压制。positionCodexPlusPage 只读取缩放,不在每次 ResizeObserver 回调中写入 zoom。
  • 观察器随宿主节点重建重新绑定,内置/拓展页面切换或关闭时释放;保留拓展 onCleanup 且只调用一次。没有有效 main 几何时保留 rail/侧栏顶部兜底,并有测试覆盖。
  • CSS 版本明确升级为 26,覆盖已安装的 main 版 25。回归测试验证 23/24/25 → 26 的替换与 26 的幂等安装。
  • 源分片为唯一修改入口,按当前 manifest 重新组装 18 个分片;扫描保留拓展注册中心的排除规则,menubar 使用简单选择器,移除 mutation 热路径中的 :has()。

注入负担与退避

保留主动 /backend/status 探测,但与已合入的 5bb4f636 持续健康退避规则统一组合,不采用“一次成功/异常就清零”的逻辑:

  • 被动心跳显示失效时,先解析主动探测结果,再更新健康计数与退避,避免先记录不健康、再记录健康破坏持续健康计时。
  • 明确成功:清理失败计数,调用 observe_healthy;持续健康满 60 秒才重置退避。
  • 明确失败:调用 observe_unhealthy 并按既有阈值/退避决定是否重注入。
  • 1500 ms 探测超时、renderer 异常、CDP 错误或非布尔结果:视为不确定;不修改失败计数、退避次数、截止时间或健康起点,也不追加重注入工作。
  • 应用实例变化仍保留立即恢复路径。

模型白名单刷新限定为有界重试,保留上游 app-server miss 熔断。移除已被上游 TypeScript 检查覆盖的旧模型导入声明 AST 断言。

另附上游浏览器绑定自检的最小路径修复与确定性回归:新增控制路径中的压缩标识符子串不再被误判为源码漂移。该修复与 #2313 中的同一小修正一致,不引入浏览器适配新方案或合并两个 PR 的生命周期逻辑。

验证

  • core/launcher 完整测试:1426 passed,0 failed,6 ignored(core 1412,launcher 14)。
  • 前端测试:352 passed;其中 renderer/标题栏定向测试 96 passed。TypeScript、Vite、renderer 语法、分片一致性及 PR diff 检查通过。
  • Node 隔离探测测试覆盖明确成功/失败、缺失 bridge、同步异常和确定性超时;Rust 状态测试覆盖不确定探测不改状态、未满 60 秒不清零、持续健康后清零及明确失败计数。
  • 离线宿主 fixture:5 种宽度 × 3 档缩放、菜单与键盘、50 次重复扫描、浅色/深色、图标栏与旧侧栏回退、内置/自定义拓展页面、清理回调与观察器释放均通过。样式迁移和菜单标识变化另有定向测试。

忽略项未计为通过。离线 fixture 不替代真实宿主物理拖动、macOS 或长时间运行验收;本 PR 缓解已识别的重复工作,不宣称解决所有应用未响应问题。本次未安装、替换或重启真实应用,未生成新的 setup/ZIP;已有警告不作无关清理。

@BigPizzaV3

Copy link
Copy Markdown
Owner

审查了。方向认可(新版宿主的面板边界与入口位置确实需要适配),但 当前版本不能直接合 —— 基线较旧,git apply --3way 到当前 main 时产生 9 处冲突,其中至少 6 处取 PR 侧的版本等于回退 main 上已经落地的改动。

会回退 main 的冲突(举证)

位置 PR 侧 main 侧 取 PR 的后果
codexDeleteStyleVersion "22" "23"(且带注释说明为何必须递增) 新样式在旧 <style> 存在时被跳过
.codex-plus-page 定位 top/right/bottom/left: 0 带 calc(100vw / zoom - left) 与 --codex-plus-page-left 丢掉窗口缩放折算与 rail 左边界
positionCodexPlusPage 按 main 取 left/top 按 railRect.right 起铺满 + ResizeObserver 跟随 面板会与原生侧栏并排,重现「三段式」
setCodexPlusSidebarNavActive 简化单参版 带 rail 三入口(home/extensions/sponsor)+ 原生选中态压制 丢失 rail 选中态同步
导航兜底查询 仅 nav[role="navigation"] 额外排除 data-app-navigation-rail 挂到图标栏上(main 上已有注释说明此坑)
overlay click 处理 旧版 modal 分支 左面板分组导航 交互回归

这些不是文本冲突,是语义分歧 —— 说明 main 在同一区域独立演进得更远。

你独有的价值:顶部 titlebar 入口

installCodexPlusTitlebarEntry() / updateCodexPlusTitlebarStatus() 在 main 上完全没有(我搜过 codexPlusTitlebarEntryId,零命中)。main 现在走的是 rail 入口方案(installCodexPlusRailNavigation,40-backend-settings.js),把入口放在图标栏;你的方案是放在顶部「帮助」菜单之后。

这是两套入口策略,不是同一件事的两版实现,所以不是「取舍」而是「是否要新增」——如果确实要加顶部入口,它应该作为 rail 入口之外的补充,而不是替换掉上面的定位/选中态逻辑。

另外两处

  • Rust 侧(bridge.rs +24、launcher.rs +39、cdp_bridge.rs +45):健康检查前主动探测 /backend/status,与 fix(bridge): 窗口隐藏时不判定桥接失效,修复后台每分钟整页重注入 #2337(ViceEye)的目标重叠。两者都在改看门狗判定,建议先定哪一套,避免互相覆盖。
  • 注入卡顿缓解(有界重试):main 上 appServerModelRequestPatchMaxMisses 已存在(在 70-model-catalog.js 分片),你的版本可能是同一思路的早期实现,rebase 时需比对。

建议

  1. 先 rebase 到当前 main,逐处保留 main 的定位/选中态/导航逻辑,只保留你新增的 titlebar 入口部分。
  2. 入口部分请按分片结构落地(main 已把 renderer-inject.js 拆成 15 个分片,分片是唯一真实来源;入口相关代码应进 50-navigation.js),改完跑 node scripts/assemble-renderer-inject.mjs。
  3. 与 fix(bridge): 窗口隐藏时不判定桥接失效,修复后台每分钟整页重注入 #2337 作者协调健康检查那一处,避免两套实现在同一函数上打架。

我可以代为 rebase,但上面 6 处是设计取舍而非机械冲突 —— 我对你的意图只能反推,代解很可能静默丢掉一边的行为。这个仓库里已经发生过一次「基于旧基线静默回退修复」(#2202 回退了 AUMID 读取),不希望再添一例。

如果 rebase 后遇到具体冲突拿不准该保哪一侧,欢迎把冲突块贴出来,我逐条给建议。

@Yuimi-chaya

Copy link
Copy Markdown
Contributor Author

这条审查中关于“回退 main”的举证已经过时,对应的不是当前 PR HEAD eb274f259d22e64151ab943ebe2727dc9798cf68。

该版本已合入 main 的 3d6d360,并完成分片适配:

  • 样式版本已升到 24,不是评论中列出的 22。
  • 保留窗口缩放折算、--codex-plus-page-left 和从 railRect.right 开始的面板布局,不会退回“三段式”。
  • 保留 home / extensions / sponsor 的选中态同步、原生选中态压制,以及导航查询对 data-app-navigation-rail 的排除。
  • 保留上游左面板分组导航及展开页取消“返回”按钮的改动。
  • 修改已落在分片源码中,单体产物由组装脚本生成,分片一致性检查通过。

请以当前 HEAD 的最终 diff 为准重新核对,而不是据旧版本补丁判断这些上游功能被回退。

后续基于最新 main 的 rebase 和冲突处理由我自行完成,不需要你代为 rebase,也请不要代改该分支。其余设计建议可以继续讨论;这条回复不表示已同意删减现有功能或改变 PR 范围。

@Yuimi-chaya
Yuimi-chaya force-pushed the codex/sidebar-panel-stability-20260926 branch from eb274f2 to 78a0b1f Compare September 30, 2026 11:22
Yuimi-chaya added a commit to Yuimi-chaya/CodexPlusPlus that referenced this pull request Sep 30, 2026
@BigPizzaV3

Copy link
Copy Markdown
Owner

感谢这版改动——标题栏入口是 main 上完全没有的能力(codexPlusTitlebarEntryId 在 main 零命中),方向正确,分片落地方式也对(改分片 + 重组产物,没像之前几个 PR 那样只改产物)。98-scan-schedule.js 把 #codex-plus-titlebar-entry 加进 isExtensionUiNode 内置段、而不是新增 registerCodexPlusExtensionSelector,符合选择器两档登记的约定,自喂循环防线保住了。

但当前 HEAD 与 main 有两个阻断项,需要修改后才能合。

阻断项 1:codexDeleteStyleVersion 与 main 撞成同值,新 CSS 会静默失效

实测三路合并:

pre.base.js:415:   codexDeleteStyleVersion = "23";
pre.ours.js:415:   codexDeleteStyleVersion = "25";   # main(10ff2fe6)
pre.theirs.js:415: codexDeleteStyleVersion = "25";   # 本 PR
--- merged ---
415: codexDeleteStyleVersion = "25";                  # 无冲突,静默取 25

两边改了同一行、内容不同、但都写成 "25",git 判为无冲突直接取 25。后果正是 10ff2fe6 注释里那句:

installStyle 靠版本号判断页面里已有的 <style> 是否过期,不升的话新样式在旧标签存在时会被直接跳过(改了等于没改)。

installStyle(10-style.js:1-6)在 existingStyle?.dataset.codexDeleteStyleVersion === codexDeleteStyleVersion 时直接 return。任何已经装过 main 版 25 的页面,拿到合并结果后,#codex-plus-titlebar-entry 那 78 行新 CSS 与 height: calc(100vh/zoom - var(--codex-plus-page-top)) 永远不生效——表现就是标题栏入口裸奔、面板盖住标题栏,正是本 PR 要修的两个症状。

改法:rebase 时显式取 "26",并把 renderer-inject.test.ts 里断言 "25" 的用例同步改成 26,补一条「version 25 → 26」的迁移用例。

阻断项 2:看门狗主动探测的 Err(_) 分支会抵消已合入的退避修复

// launcher.rs,本 PR 新增
match bridge_active_health_probe_ok(debug_port).await {
    Ok(true) | Err(_) => {
        *health_failures = 0;
        backoff.reset();     // ← 立即清零
        return false;
    }
    Ok(false) => {}
}

5bb4f636(已合入 main)的整个落点就是「一见健康就 reset 会让退避永远停在第一档」,为此引入 BRIDGE_REINJECT_BACKOFF_RESET_AFTER_SECS = 60 + observe_healthy(),要求持续健康满 60 秒才清零,且探测不确定(None)时不改变状态。

本 PR 的 Err(_) 走了 Ok(true) 的同一分支,于是立即 reset()。注释里写「indeterminate CDP probe is not permission to add work to a busy renderer」——这个意图是对的,但实现应该对应「不改变退避状态」,而不是 reset()。

更关键的是 evaluate_bridge_health_script 新增的这段:

if result.get("result").and_then(|v| v.get("exceptionDetails")).is_some() {
    anyhow::bail!("bridge health probe raised a renderer exception");
}

renderer 抛异常 = 页面正忙 → Err → 被判为健康并清零退避。 也就是说越忙的 renderer 越会把退避清掉,方向与 5bb4f636 正好相反。

另外 bridge_active_health_probe_script 的 1500ms Promise.race 超时返回 null → result?.status !== "ok" → 返回 false(判为不健康),不是 Err。所以繁忙 renderer 有两条判负路径(超时判负 / 抛异常判正),行为不对称。

建议:把主动探测这组(bridge.rs / launcher.rs / cdp_bridge.rs)从本 PR 拆出去,单独开一个 PR 与 #2337 的作者一起定这套状态机。本 PR 正文也写了「未合入 #2337,也不声称两者已完成组合验证」——在一个未完成组合验证的看门狗改动上同时改 health_failures 与 backoff 两条状态机,风险不划算。

如果保留,至少要改成:Ok(true) → observe_healthy、Ok(false) → observe_unhealthy、Err(_) → 不改变状态。

其他需要处理的

必须

  1. includeHome=false 时三个入口可能同时缺失的空窗:installCodexPlusNavigationEntries 里先 installCodexPlusTitlebarEntry()(它会 remove 掉 rail 入口),若此时 installCodexPlusRailNavigation 因宿主 rail 尚未渲染返回 false,控制流落到 installCodexPlusSidebarNavigation(titlebarInstalled=true),而该函数在 if (titlebarInstalled) return; 处在创建 wrapper 之前就返回了——这一帧三个入口一个都不存在。补扫定时器会在下一轮兜住,窗口很短,但建议把该判断挪到 wrapper 判定之后,或让 titlebar 分支走同一条 activeEntry 同步路径。

  2. positionCodexPlusPage 现在每次调用都会执行 applyCodexPlusZoom(overlay)。该函数有副作用(写 --codex-plus-zoom 与内联 zoom),而原实现用的是无副作用的 codexPlusWindowZoom() 取值。这个函数被 ResizeObserver 每次回调调用,等于持续往 overlay 写内联 zoom。功能上大概率无害(值相同),但把「读」换成了「写」,建议改回纯读。

  3. positionCodexPlusPage 的 top 兜底链里 railRect 分支是死分支(rail 存在意味着新宿主,main 几乎必然存在)。

建议

  1. application-menu-trigger-help-menu 是 -trigger-<name> 派生名,不是上游明确定义为契约的 data-*。上游改名或换语种 slot 时入口会静默消失(installCodexPlusTitlebarEntry() 返回 false 回落 rail,用户无感但功能没了)。建议加一条结构兜底(如 [role="menubar"] 内最后一个 [role="menuitem"]),或至少在失败时写一条诊断日志——成本极低,事后能定位。

  2. [role="menubar"]:has(#application-menu-trigger-help-menu) 进了 scanRelevantSelector(),每次 mutation 都会 querySelector 一次;:has() 在这条路径上偏贵,建议实测最坏情形。

  3. model-metadata.test.ts 那 28 行对上游 App.tsx 做 AST 断言(检查 metadataImportPreview 只声明一次)建议删掉:该 TS2451 是上游 d90c0c0 自带的、最新上游已删除,而重复 useState 本来就会被 tsc 直接拦住。这类「对着上游源码做 AST 断言」的测试会在上游每次重构时误报。

  4. codexPlusBridgeFailureCount >= CODEX_PLUS_BRIDGE_FAILURE_THRESHOLD 在 titlebar 里被读,但注入层从未累加该变量,"degraded" 状态不可达;CSS 却为它留了规则。要么接上真实计数源,要么删掉这条分支。

  5. aria-current="false" 是非法值,应为 removeAttribute("aria-current")(main 既有写法,本 PR 扩到了新按钮上)。

关于拆分

1039 行混了 5 个关注点。建议拆成独立 PR:

组 内容 建议
A 标题栏入口 + 顶部定位(50-navigation.js / 98-scan-schedule.js / titlebar CSS / titlebar-entry.test.ts) ✅ 单独提 PR,大概率能很快合
B 面板边界 / 缩放 / ResizeObserver ✅ 独立
C 看门狗主动探测(bridge.rs / launcher.rs / cdp_bridge.rs) ⚠️ 建议撤出,单独讨论
D 模型白名单有界重试(70-model-catalog.js) 可并入或独立小 PR
E 上游回归的 AST 测试 ❌ 建议删掉

与 #2313 的关系

同一作者,但必须保持两个独立 PR:#2313 改启动/重启生命周期(native_browser.rs / watcher.rs / commands.rs),本 PR 改注入层运行时健康检测。两者唯一交集是都动了 launcher 周边,但合成一个会让「启动生命周期」与「注入健康检查」的语义分歧纠缠在一起,回退粒度也变粗。

BigPizzaV3 added a commit that referenced this pull request Oct 2, 2026
审计当时的处置建议有两处被实测推翻、一处性质被更正,记下来:

- **推翻 #2303 的「并发验证阻断项」**:默认并发 5 次全过(未复现 PR 描述的失败);
  32 线程加压下 main 与 PR 分支会挂**同一个**用例
  (upstream_request_returns_when_provider_accepts_but_never_sends_headers,
  墙钟断言 assert!(started.elapsed() < 1s))。是既有 flake,不是 PR 引入。
  真正阻断项是分支落后 34 提交。
  另更正「保留 compaction: bool 单一标记路线」这条约束——加入原生透传后
  compaction 语义已分裂,多字段反而更清晰。
- **推翻 #2313 的「范围过大需拆分」**:冲突只有 1 处且是陈旧基线
  (main 的 04ab629 把 recovery_material 改成 2 参),解完整树全绿 1395/75/331。
  不该因冲突要求改设计。
- **补充 #2333 的真实回归**(审计未发现):实测 model_catalog 测试红,
  默认模型被静默换成 modelList 第一条。附教训:审查贡献者 PR 时必须单独验
  「PR 自带测试是否覆盖整个 workspace」,只信 PR 描述的测试命令会漏跨 target 回归。
- 补记 #2309 两个阻断项的实测证据(版本号撞车、Err(_) 抵消退避修复)。

执行结果:#2366 已合入(ea82bc74);#2337 / #2371 由已落地提交覆盖后关闭;
#2333 / #2309 回复要求修改;#2313 / #2303 回复可合但需先 rebase。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Yuimi-chaya

Copy link
Copy Markdown
Contributor Author

已 rebase 到 main 的 ab203409,当前 HEAD 为 d5eff418,保持与 #2313 独立。

两个阻断项均已处理:

  • 样式版本升为 26,补齐 main 版 25 → 26 的迁移,并保留 23/24 升级与 26 幂等用例。
  • 主动探测保留,但已经与上游 5bb4f636 的持续健康退避统一组合:先得到有效探测结果,再更新两条状态;成功调用 observe_healthy,明确失败调用 observe_unhealthy,不确定结果不改状态。Promise 超时和异常都返回 null,不会一边超时判负、一边异常清零。Rust 回归直接核对失败计数、退避次数、截止时间和健康起点;Node 测试覆盖真实探测脚本的成功/失败/异常/确定性超时。

其余项目也已处理:

  • 不再提前拆掉旧入口;rail 暂缺时标题栏仍挂载并走 activeEntry 同步,补了 50 次扫描回归。
  • 定位函数改为纯读缩放,保留 main 几何暂不可用时的 rail/侧栏顶部回退;现有测试覆盖 main 零尺寸情形,所以未删除这个有用的分支。
  • 增加原生 menubar/最后一个 menuitem 的结构兜底,并测试帮助标识变化;热路径选择器移除 :has()。
  • 删除模型导入的旧 AST 源码断言;标题栏删掉不可达的降级计数分支,非当前按钮移除 aria-current。
  • 保留上游插件过滤、拓展/推荐入口和完整拓展清理契约,重新组装 18 个分片。

测试还发现上游浏览器绑定计数对新增控制路径子串的误判,已单独小提交补偿路径字面量并加确定性回归,与 #2313 中同一修正一致;两份 PR 的主体范围没有合并。

本地结果:core 1412 + launcher 14,共 1426 passed/0 failed/6 ignored;前端 352 passed,定向 96 passed,TypeScript、Vite、语法/组装/diff 检查通过。离线宿主 fixture 的 5 种宽度 × 3 档缩放、菜单/键盘、页面切换、扩展清理与回退检查通过。

正文已更新实际退避组合与验证结果,不再使用“未组合验证 #2337”的过时表述。没有关闭/重启/替换真实应用,未做 macOS 或真实宿主长期运行验收;忽略项未计为通过。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants