fix(coding-agent): prefer subscription accounts with headroom
Summary
Prefer subscription accounts with reported headroom before starting a request.
Part of code-yeongyu/oh-my-openagent#9234. Account-list usage display is delivered separately.
Root cause
Both the generic credential pool and the Claude subscription selector ranked accounts without consulting the latest reported subscription windows. A session could therefore keep selecting an account already at its limit while another account still had allowance.
Fix
- Share a pure headroom preference between both selection paths.
- Prefer the next usable account when any live window reports at least 95% usage; ignore reset windows and missing data.
- Keep pins authoritative and preserve the current choice when every usable account is near its limit.
- Retain the replacement account for session cache locality, without changing failure classification or block lifetimes.
- Preserve Claude's explicit disabled-account-fallback setting.
Evidence
- RED against the original selectors: 6 failed, 14 passed.
- GREEN regression: 24 passed, 0 failed.
- Changed-file
oxfmt --checkandoxlint: passed. - Full
bun run checkwas not run under the lane's host-load restriction. Broad static validation is delegated to the post-push lane receipt. - Tests use synthetic accounts, a fake clock, and fake limit snapshots; no network or live model calls.
Summary by cubic
Fixes account selection so both the credential pool and the Claude subscription selector prefer accounts with reported headroom over ones already at their limit.
- Skips accounts whose live usage window reports at least 95%; reset and missing windows are ignored.
- Preserves pinned accounts, the current choice when every account is near its limit, and Claude's disabled-account-fallback setting.
- Remembers the replacement account for session cache locality only; failure classification and block lifetimes are unchanged.
- Adds regression tests covering both selection paths, seeding headroom Claude accounts as CLI-store slots.
Still open: typed blocked-account surface
This PR does not deliver the typed RPC/desktop surface that says why an account is blocked, until when, and what to do. That is the one open item for account visibility. It is tracked on #4092 (blocked on #4992 and #4674) and starts as soon as those two merge.