Skip to content

跨账号展示并手动消耗 Codex 重置卡 - #47

Merged
lizhelang merged 1 commit into
lizhelang:mainfrom
seansun05:feat/rate-limit-reset-credits
Sep 6, 2026
Merged

跨账号展示并手动消耗 Codex 重置卡#47
lizhelang merged 1 commit into
lizhelang:mainfrom
seansun05:feat/rate-limit-reset-credits

Conversation

@seansun05

@seansun05 seansun05 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 在菜单里跨账号查看 Codex 储存的重置卡,默认只展示最快到期的一张;悬停或点箭头可在侧边面板看完整列表。
  • 支持手动消耗重置卡,用完后自动刷新该账号额度,不会为用卡切换当前写代码的账号。
  • 72 小时内到期会在菜单栏图标上提示,24 小时内会发系统通知;同时压住菜单打开时的高度回跳。

Test plan

  • 打开菜单,确认默认只显示 1 张最快到期的重置卡,账号行用量后面不再出现「N张」摘要
  • 悬停重置卡区域或点右上角箭头,侧边面板能列出全部卡,滚动条隐藏但仍可滚动
  • 确认消耗一张卡后,该账号额度会自动刷新,且不会切换当前账号
  • 重复打开菜单,确认不会持续抖动;打开瞬间也不应再明显跳一下
  • 有 24 小时内到期的卡时,确认系统通知只对同一张卡提示一次

Made with Cursor

打开菜单即可查看各账号储存的重置机会,默认用最快到期的一张;
消耗后只刷新该账号额度,不为用卡切号。同时压住菜单打开时的高度回跳。

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review:合入前建议处理的问题

整体功能闭环基本到位(跨账号重置卡、默认只露最快到期、侧边面板、手动消耗且不切写代码账号、72h 角标、24h 通知去重、菜单高度防抖)。下面按优先级列出已核实的问题。


阻塞 / 建议合入前修

1. 消耗成功后刷新可能静默失败
consumeResetCredit 成功后走 refreshAccount(..., announceResult: false)。若撞上菜单打开时的全量刷新,refreshOneRetryingIfSkipped 最多约 2.5s 后仍可能返回 .skipped,且 skipped 没有 errorMessage,界面不报错。结果是:卡已扣,额度/卡列表可能不更新,用户会以为「用了没效果」。
建议:最终仍 skipped / 失败时给可见反馈,或更明确地重试/排队到占用释放后再刷。

2. 确认弹窗文案写死「5h / 每周」
L.resetCreditConfirmMessage 把主/次窗口名写死为「5h」「每周」。对 Plus 大致对,但免费账号主窗口可能是 7 天(仓库已有 testFreeAccountTreatsPrimaryAsWeeklyWhenApiSaysSevenDays)。RateLimitResetCreditItem 目前只带用量百分比、不带 primaryLimitWindowSeconds / secondaryLimitWindowSeconds,无法动态描述;免费号往往没有 secondary,还会多出误导的「每周已用 0%」。
建议:用窗口秒数(或现有 windowLabel)动态生成文案,或至少按实际窗口结构分文案。


重要

3. 无 expiresAt 的 available 卡对 UI 不可见
RateLimitResetCreditPresentation.items 要求必须有 expiresAt,否则丢弃;但 isAvailable(now:) 允许无过期时间。会出现 count>0,列表/角标/通知全空。需明确策略:展示、单独排序,或解析时过滤并下调 count。

4. 详情接口失败时可能「有数量、无列表」
WhamService.loadResetCreditsIfNeeded 失败会保留旧 credits,却写入 usage 返回的 availableCount。首次就有卡且详情失败时,界面完全看不到卡。

5. refreshNow() 行为被放大成刷全量
本次改动里 force: true 会直接走 refreshAll。账号切换后 activateAccount 会调 OpenAIUsagePollingService.shared.refreshNow(),等于从「刷当前账号」扩成「刷全部」。若这是有意(为跨账号重置卡保持新鲜),请在 PR 说明里写清;否则建议切号路径仍只刷目标账号,全量刷新留给定时策略。

6. 行为侧改动单测覆盖不到,需实机确认

  • 菜单防跳:presentPopoverAfterInitialSizing 三次主队列延迟、把 popoverWillShow 提前到真正显示前、高度 resize 一律 animated: false、4pt 阈值忽略噪声。请实机反复打开菜单确认不抖、打开瞬间不跳。
  • 24h 系统通知:同卡只提示一次(含授权后真实投递)。
  • 消耗后额度刷新且不切写代码账号。
  • Test plan 目前全未勾;分支上也看不到 CI checks。模型层单测够用,但消耗闭环 / UX 不能靠单测替代。

7. 私有 API + 破坏性 consume(风险观察,非代码缺陷)
三个 chatgpt.com/backend-api/wham/... 端点与现有 usage 拉取同路;consume 会真扣卡。codereset / nothing_to_reset / no_credit / already_redeemed)是猜测字段名——有 .unknown 兜底和失败提示,一般不会乱扣,但合入后请重点实机观察一次真实消费流程与返回字段是否仍匹配。


小清理(可不阻塞,建议顺手)

  • L.resetCreditUsed(「已使用重置卡…」)定义了但从未使用——因为用卡后 announceResult: false,成功无明确播报。
  • RateLimitResetConsumeResult.windowsReset 解析了但未使用。
  • RateLimitResetNotificationService.authorizationRequestedDefaultsKey 只写不读。
  • RateLimitResetCreditPresentation.accountRowSummary 有实现与单测,UI 未调用(符合「账号行不再显示 N张」,但是死代码)。
  • 通知服务 isEvaluating 期间后续 evaluate 直接丢弃,可能漏发;notifiedKeys 只增不清理,长期会膨胀。

建议

先修 #1(消耗后刷新静默失败)#2(确认文案窗口名),再明确 #3/#4/#5 的预期行为;然后按 Test plan 做一轮实机回归(尤其防跳、真实扣卡、通知只弹一次)后再合。

@lizhelang

Copy link
Copy Markdown
Owner

实机验证 + 修复(见 PR #48

在真实 macOS(27.0 / Xcode 26.6)上构建并运行了本分支,逐项实测后确认了以下问题,并已在 PR #48(基于本分支代码快照 pr47-base)提交修复:

  1. 确认弹窗文案写死「5h / 每周」resetCreditConfirmMessage 硬编码窗口名,且不判断次级窗口;对 FREE(30d)/TEAM/PROLITE 会误导并编造「每周已用 X%」。已改为按实际窗口秒数动态生成。
  2. 用卡成功后刷新失败/被跳过无反馈consumeResetCreditannounceResult:false 吞掉刷新失败,L.resetCreditUsed 死文案未接入。已接入成功播报并让刷新失败可见。
  3. expiresAt / 详情失败 → count>0 但列表空白:已加兜底提示,不再整卡隐掉。
  4. 切号后 refreshNow() 变成全量刷新:原 main 只刷活跃账号;本分支切号即刷全部。已改回只刷活跃账号,5 分钟全量刷保留为后台节奏。
  5. 死代码authorizationRequestedDefaultsKeyaccountRowSummaryL.resetCreditAccountSummary 已清理;windowsReset 接入。

测试:目标用例 52 通过,全量 607 通过 0 失败。

说明:受「不直接推 fork 分支」约束,PR #48pr47-base(本分支代码快照)为 base。若本分支先合入 main,PR #48 期望 rebase 到最新 main 后仅剩修复 diff。

@lizhelang
lizhelang merged commit 50dab6e into lizhelang:main Sep 6, 2026
lizhelang added a commit that referenced this pull request Sep 6, 2026
针对 PR #47(跨账号展示并手动消耗 Codex 重置卡)在本机构建、运行并逐项实测,确认以下问题并修复:

1. 用卡成功后刷新失败/被跳过缺失可见反馈
   - consumeResetCredit 成功用卡后 refreshAccount(announceResult:false) 会吞掉刷新失败;且 L.resetCreditUsed 死文案从未接入。
   - 改为成功用卡先播报 L.resetCreditUsed(windowsReset),再 announceResult:true 让刷新失败可见;已 .alreadyRedeemed 单独播报;新增 .notice 状态用绿色对勾横幅区分成功与错误。

2. 确认弹窗窗口名写死"5h / 每周"
   - resetCreditConfirmMessage 硬编码窗口名,且未判断次级窗口,对 FREE/TEAM/PROLITE 会误导并编造"每周已用 X%"。
   - 改为按 primary/secondaryLimitWindowSeconds 沿用 windowLabel 动态生成;次级窗口不存在时不拼接。RateLimitResetCreditItem 增加窗口秒数字段。

3. 无 expiresAt 卡 / 详情失败导致 count>0 但列表空白
   - items 在无 expiresAt 时整卡丢弃;loadResetCreditsIfNeeded 失败时复用可能为空的旧 credits。
   - 新增 resetCreditTotalAvailableCount 兜底,有数量但无可展示卡时给出提示,不再整体隐掉。

4. 切号后 refreshNow() 变成全量刷新
   - 原 main 为只刷活跃账号;PR 让 force 触发 refreshAll,切号即刷全部。
   - refreshNow() 改回只刷当前活跃账号;后台 5 分钟全量刷保留为独立节奏。

5. 死代码清理
   - 删除 authorizationRequestedDefaultsKey(只写不读)、accountRowSummary 与 L.resetCreditAccountSummary(未使用且与"账号行不显示 N张摘要"目标一致);windowsReset 接入成功播报;L.resetCreditUsed 接入成功播报。

实测:目标用例 52 通过,全量 607 通过 0 失败。验证报告见 docs/plans/2026-09-06-pr47-reset-credits-verification.md。
lizhelang added a commit that referenced this pull request Sep 6, 2026
* 实机验证 PR #47 并修复重置卡相关问题

针对 PR #47(跨账号展示并手动消耗 Codex 重置卡)在本机构建、运行并逐项实测,确认以下问题并修复:

1. 用卡成功后刷新失败/被跳过缺失可见反馈
   - consumeResetCredit 成功用卡后 refreshAccount(announceResult:false) 会吞掉刷新失败;且 L.resetCreditUsed 死文案从未接入。
   - 改为成功用卡先播报 L.resetCreditUsed(windowsReset),再 announceResult:true 让刷新失败可见;已 .alreadyRedeemed 单独播报;新增 .notice 状态用绿色对勾横幅区分成功与错误。

2. 确认弹窗窗口名写死"5h / 每周"
   - resetCreditConfirmMessage 硬编码窗口名,且未判断次级窗口,对 FREE/TEAM/PROLITE 会误导并编造"每周已用 X%"。
   - 改为按 primary/secondaryLimitWindowSeconds 沿用 windowLabel 动态生成;次级窗口不存在时不拼接。RateLimitResetCreditItem 增加窗口秒数字段。

3. 无 expiresAt 卡 / 详情失败导致 count>0 但列表空白
   - items 在无 expiresAt 时整卡丢弃;loadResetCreditsIfNeeded 失败时复用可能为空的旧 credits。
   - 新增 resetCreditTotalAvailableCount 兜底,有数量但无可展示卡时给出提示,不再整体隐掉。

4. 切号后 refreshNow() 变成全量刷新
   - 原 main 为只刷活跃账号;PR 让 force 触发 refreshAll,切号即刷全部。
   - refreshNow() 改回只刷当前活跃账号;后台 5 分钟全量刷保留为独立节奏。

5. 死代码清理
   - 删除 authorizationRequestedDefaultsKey(只写不读)、accountRowSummary 与 L.resetCreditAccountSummary(未使用且与"账号行不显示 N张摘要"目标一致);windowsReset 接入成功播报;L.resetCreditUsed 接入成功播报。

实测:目标用例 52 通过,全量 607 通过 0 失败。验证报告见 docs/plans/2026-09-06-pr47-reset-credits-verification.md。

* 验证报告:修正证据路径说明,统一指向 output/pr47-evidence
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